diff --git a/scripts/check_crash_newmonkey.py b/scripts/check_crash_newmonkey.py new file mode 100644 index 0000000..d66a709 --- /dev/null +++ b/scripts/check_crash_newmonkey.py @@ -0,0 +1,841 @@ +# This file aims to do quick crash checking + +import csv +import datetime +import os +import shutil +import subprocess +import time +from argparse import ArgumentParser, Namespace +from typing import List, Dict, Set + +ALL_APPS = ['ActivityDiary', 'AmazeFileManager', 'and-bible', 'AnkiDroid', 'APhotoManager', 'commons', + 'collect', 'FirefoxLite', 'Frost', 'geohashdroid', 'MaterialFBook', 'nextcloud', 'Omni-Notes', + 'open-event-attendee-android', 'openlauncher', 'osmeditor4android', 'Phonograph', 'Scarlet-Notes', + 'sunflower', 'WordPress'] + +app_crash_data = { + + 'AnkiDroid': { + + '#4200': ['com.ichi2.libanki.Note com.ichi2.libanki.Card.note()', + 'com.ichi2.async.DeckTask.doInBackgroundUpdateNote(DeckTask.java', + 'com.ichi2.async.DeckTask.doInBackground(DeckTask.java'], + + '#4451': [ + 'android.support.design.widget.CoordinatorLayout$LayoutParams cannot be cast to android.widget.RelativeLayout$LayoutParams', + 'com.ichi2.anki.AbstractFlashcardViewer.initLayout(AbstractFlashcardViewer.java', + 'com.ichi2.anki.AbstractFlashcardViewer.onCollectionLoaded(AbstractFlashcardViewer.java', + 'com.ichi2.anki.Reviewer.onCollectionLoaded(Reviewer.java', + 'com.ichi2.anki.AnkiActivity.onLoadFinished(AnkiActivity.java'], + + '#5638': ['com.ichi2.libanki.Utils.stripHTMLMedia', + 'com.ichi2.libanki.Utils.stripHTML'], + + '#4707': ['exposed beyond app through ClipData.Item.getUri()', + 'com.ichi2.anki.AnkiActivity.startActivityForResult(AnkiActivity.java', + 'com.ichi2.anki.multimediacard.fields.BasicImageFieldController$2.onClick(BasicImageFieldController.java'], + + '#6145': ['com.ichi2.libanki.AnkiPackageExporter.exportInto(AnkiPackageExporter.java'], + + '#5756': ['Unable to start activity ComponentInfo{com.ichi2.anki/com.ichi2.anki.Reviewer}', + 'com.ichi2.anki.AbstractFlashcardViewer.restoreCollectionPreferences(AbstractFlashcardViewer.java', + 'com.ichi2.anki.AbstractFlashcardViewer.onCollectionLoaded(AbstractFlashcardViewer.java', + 'com.ichi2.anki.AnkiActivity.startLoadingCollection(AnkiActivity.java'], + + '#4977': [ + "Attempt to invoke virtual method 'boolean android.support.v7.widget.SearchView.isIconified()' on a null object reference", + 'com.ichi2.anki.CardBrowser$20.onPostExecute(CardBrowser.java', + 'com.ichi2.async.DeckTask$TaskListener.onPostExecute(DeckTask.java', + 'com.ichi2.async.DeckTask.onPostExecute(DeckTask.java'] + }, + + 'ActivityDiary': { + + '#118': ['java.lang.IllegalArgumentException: position (', + 'de.rampro.activitydiary.ui.generic.DetailRecyclerViewAdapter.getDiaryImageIdAt(DetailRecyclerViewAdapter.java', + 'de.rampro.activitydiary.ui.history.HistoryRecyclerViewAdapter$1.onClick(HistoryRecyclerViewAdapter.java'], + + '#285': ['java.lang.NumberFormatException: For input string', + 'de.rampro.activitydiary.ui.settings.SettingsActivity.updateLocationAge(SettingsActivity.java', + 'de.rampro.activitydiary.ui.settings.SettingsActivity.onSharedPreferenceChanged(SettingsActivity.java'] + }, + + 'geohashdroid': { + + '#73': ['java.lang.RuntimeException: An error occurred while executing doInBackground()', + 'java.lang.String net.exclaimindustries.geohashdroid.util.Graticule.getLatitudeString(boolean)', + 'net.exclaimindustries.geohashdroid.util.HashBuilder$StockRunner.runStock(HashBuilder.java', + 'net.exclaimindustries.geohashdroid.services.StockService.onHandleWork(StockService.java'] + }, + + 'and-bible': { + + '#261': ['java.lang.StackOverflowError: stack size', + 'org.crosswire.jsword.index.lucene.LuceneIndex.generateSearchIndexImpl(LuceneIndex.java'], + + '#375': ['kotlin.TypeCastException: null cannot be cast to non-null type org.crosswire.jsword.book.Book', + 'net.bible.service.history.HistoryManager.setDumpString(HistoryManager.kt', + 'net.bible.android.view.activity.page.MainBibleActivity.openTab(MainBibleActivity.kt'], + + '#480': ['net.bible.service.db.bookmark.BookmarkDBAdapter.updateLabel(BookmarkDBAdapter.kt', + 'net.bible.android.control.bookmark.BookmarkControl.saveOrUpdateLabel(BookmarkControl.kt', + 'net.bible.android.view.activity.bookmark.LabelDialogs$1.onClick(LabelDialogs.java'], + + '#697': [ + 'Unable to start activity ComponentInfo{net.bible.android.activity/net.bible.android.view.activity.mynote.MyNotes}', + 'net.bible.android.control.versification.sort.VersificationPrioritiser.getVersifications(VersificationPrioritiser.java', + 'net.bible.android.control.versification.sort.ConvertibleVerseRangeComparator$Builder.withMyNotes(ConvertibleVerseRangeComparator.java'], + + '#703': ['org.crosswire.jsword.index.IndexStatus org.crosswire.jsword.book.Book.getIndexStatus()', + 'net.bible.android.view.activity.search.SearchIndexProgressStatus.jobFinished(SearchIndexProgressStatus.java', + 'net.bible.android.view.activity.base.ProgressActivityBase$initialiseView$uiUpdaterRunnable$1.run(ProgressActivityBase.kt'] + }, + + 'AmazeFileManager': { + + '#1232': ['Failure delivering result ResultInfo', + "Attempt to invoke virtual method 'java.lang.Object java.util.ArrayList.get(int)' on a null object reference", + 'com.amaze.filemanager.activities.MainActivity.onActivityResult(MainActivity.java', + ], + '#1558': ["com.amaze.filemanager.exceptions.StreamNotFoundException: Can't get stream", + "com.amaze.filemanager.filesystem.FileUtil.getOutputStream(FileUtil.java", + "com.amaze.filemanager.filesystem.FileUtil.isWritable(FileUtil.java", + 'com.amaze.filemanager.asynchronous.asynctasks.WriteFileAbstraction.doInBackground'], + + '#1796': ["java.lang.IndexOutOfBoundsException: Index:", + 'com.amaze.filemanager.asynchronous.asynctasks.DeleteTask.onPostExecute(DeleteTask.java'], + + '#1837': ['java.lang.IndexOutOfBoundsException: Index:', + 'com.amaze.filemanager.adapters.glide.RecyclerPreloadModelProvider.getPreloadItems(RecyclerPreloadModelProvider.java', + 'com.bumptech.glide.ListPreloader.preload(ListPreloader.java', + 'com.bumptech.glide.integration.recyclerview.RecyclerToListViewScrollListener.onScrolled(RecyclerToListViewScrollListener.java'] + }, + + 'FirefoxLite': { + + '#4881': ['org.json.JSONException: End of input at character', + 'org.mozilla.rocket.util.JsonUtilsKt.toJsonArray(JsonUtils.kt', + 'org.mozilla.rocket.home.contenthub.data.ContentHubRepoKt.jsonStringToTypeList(ContentHubRepo.kt', + 'org.mozilla.rocket.home.contenthub.data.ContentHubRepo$getReadTypesLive$1.invoke(ContentHubRepo.kt', + 'org.mozilla.rocket.extension.LiveDataExtensionKt$sam$androidx_arch_core_util_Function$0.apply(LiveDataExtension.kt)', + 'org.mozilla.focus.activity.MainActivity.onStart(MainActivity.kt', + ], + + '#4942': [ + 'java.lang.RuntimeException: Cannot create an instance of class org.mozilla.rocket.home.HomeViewModel', + 'java.lang.InstantiationException: java.lang.Class has no zero argument constructor', + 'org.mozilla.focus.tabs.tabtray.TabTrayFragment.onCreateView(TabTrayFragment.java'], + + '#5085': [ + "java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.os.BaseBundle.getString(java.lang.String)' on a null object reference", + 'org.mozilla.focus.settings.SettingsFragment.onCreate(SettingsFragment.kt'] + + }, + + 'open-event-attendee-android': { + '#2198': [ + 'org.fossasia.openevent.general.search.SearchFragment$onCreateView$3.onClick(SearchFragment.kt', + # special handling for combo, which discarded the line number on this case + # 'org.fossasia.openevent.general.search.SearchFilterFragment$setFilters$3.onClick(SearchFilterFragment.kt:127)', + 'org.fossasia.openevent.general.search.type.SearchTypeFragment.(SearchTypeFragment.kt'] + }, + + 'openlauncher': { + '#67': ['java.lang.SecurityException: Not allowed to change Do Not Disturb state', + 'com.benny.openlauncher.util.LauncherAction.RunAction(LauncherAction.java', + 'com.benny.openlauncher.activity.Home$11.onItemClick(Home.java'] + }, + + 'APhotoManager': { + '#116': ['Error inflating class de.k3b.android.widgets.EditTextPreferenceWithSummary', + 'de.k3b.android.androFotoFinder.SettingsActivity.onCreate(SettingsActivity.java'] + }, + + 'sunflower': { + '#239': [ + 'java.lang.IllegalArgumentException: navigation destination com.google.samples.apps.sunflower:id/action_plant_list_fragment_to_plant_detail_fragment is unknown to this NavController', + 'com.google.samples.apps.sunflower.adapters.PlantAdapter$createOnClickListener$1.onClick(PlantAdapter.kt'] + }, + + 'collect': { + '#3222': ["java.lang.IllegalArgumentException: column 'MAX(date)' does not exist", + + 'org.odk.collect.android.activities.FormChooserList.onLoadFinished(FormChooserList.java'] + }, + + 'MaterialFBook': { + '#224': ['android.content.res.Resources$NotFoundException: Resource ID #0x20b001b', + 'org.chromium.ui.base.DeviceFormFactor.b(PG', + 'org.chromium.ui.base.DeviceFormFactor.a(PG', + 'bCE.onCreateActionMode(PG', + 'org.chromium.content.browser.selection.SelectionPopupControllerImpl.y(PG', + 'org.chromium.content.browser.selection.SelectionPopupControllerImpl.showSelectionMenu(PG'] + }, + + 'Omni-Notes': { + '#745': ['No virtual method fitCenter()Lcom/bumptech/glide/request/RequestOptions', + 'it.feio.android.simplegallery.GalleryPagerFragment.onCreateView(GalleryPagerFragment.java'] + }, + + 'OmniNotes': { + '#745': ['No virtual method fitCenter()Lcom/bumptech/glide/request/RequestOptions', + 'it.feio.android.simplegallery.GalleryPagerFragment.onCreateView(GalleryPagerFragment.java'] + }, + + 'Phonograph': { + '#112': ['com.kabouzeid.gramophone.appshortcuts.AppShortcutLauncherActivity.startServiceWithSongs'] + }, + + 'osmeditor': { + '#637': [ + "Attempt to invoke interface method 'java.util.Set java.util.Map.entrySet()' on a null object reference"], + + '#705': [ + "android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views", + "de.blau.android.propertyeditor.PresetFragment$3.doInBackground(PresetFragment.java:258)", + "de.blau.android.propertyeditor.PresetFragment$3.doInBackground(PresetFragment.java:244)"], + + '#729': [ + "android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views", + "de.blau.android.propertyeditor.PresetFragment$3.doInBackground(PresetFragment.java:258)", + "de.blau.android.propertyeditor.PresetFragment$3.doInBackground(PresetFragment.java:244)"] + }, + + 'Scarlet-Notes': { + '#114': ['java.lang.Exception: Invalid Search Mode', + 'com.maubis.scarlet.base.support.SearchConfigKt.getNotesForMode(SearchConfig.kt', + 'com.maubis.scarlet.base.support.SearchConfigKt.filterSearchWithoutFolder(SearchConfig.kt', + 'com.maubis.scarlet.base.support.SearchConfigKt.unifiedSearchSynchronous(SearchConfig.kt', + 'com.maubis.scarlet.base.MainActivity$unifiedSearchSynchronous$$inlined$map$lambda$1.invokeSuspend(MainActivity.kt'] + + }, + + 'Frost': { + + '#1323': [ + "java.net.UnknownHostException: Unable to resolve host \"m.facebook.com\": No address associated with hostname", + "com.pitchedapps.frost.fragments.FrostParserFragment.reloadImpl$suspendImpl(RecyclerFragmentBase.kt", + "com.pitchedapps.frost.fragments.FrostParserFragment.reloadImpl(Unknown Source", + "com.pitchedapps.frost.fragments.RecyclerFragment$reload$2.invokeSuspend(RecyclerFragmentBase.kt"] + }, + + 'commons': { + '#1385': ['java.lang.NullPointerException: Callable returned null'], + '#1391': ["fr.free.nrw.commons.nearby.NearbyMapFragment$8.onStateChanged(NearbyMapFragment.java"], + + '#1581': [ + "java.lang.NullPointerException: Attempt to invoke virtual method 'double android.location.Location.getLatitude()' on a null object reference", + "fr.free.nrw.commons.location.LatLng.from(LatLng.java", + 'fr.free.nrw.commons.location.LocationServiceManager.getLKL(LocationServiceManager.java', + 'fr.free.nrw.commons.nearby.NearbyActivity.onRequestPermissionsResult(NearbyActivity.java'], + + '#2123': [ + "java.lang.NullPointerException: Attempt to invoke virtual method 'android.support.v4.app.FragmentActivity android.support.v4.app.Fragment.getActivity()' on a null object reference", + 'fr.free.nrw.commons.media.MediaDetailPagerFragment$MediaDetailAdapter.getItem(MediaDetailPagerFragment.java'], + '#3244': ['fr.free.nrw.commons.upload.UploadActivity.receiveInternalSharedItems(UploadActivity.java'] + }, + + 'nextcloud': { + '#1918': [ + 'java.lang.ClassCastException: com.owncloud.android.ui.preview.PreviewImageActivity cannot be cast to com.owncloud.android.ui.activity.FileDisplayActivity', + 'com.owncloud.android.ui.preview.PreviewImageFragment.onOptionsItemSelected(PreviewImageFragment.java'], + + '#4026': [ + 'java.lang.RuntimeException: Unable to start activity ComponentInfo{com.nextcloud.client/com.owncloud.android.ui.activity.FileDisplayActivity}', + 'com.owncloud.android.ui.activity.ToolbarActivity.setupToolbar(ToolbarActivity.java', + 'com.owncloud.android.ui.activity.FileDisplayActivity.onCreate(FileDisplayActivity.java'], + + '#4792': [ + "java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String com.owncloud.android.datamodel.OCFile.getRemotePath()' on a null object reference", + 'com.owncloud.android.ui.dialog.CreateFolderDialogFragment.onClick(CreateFolderDialogFragment.java'], + + '#5173': ['android.view.MenuItem android.view.MenuItem.setVisible(boolean)', + 'com.owncloud.android.ui.fragment.OCFileListFragment.onPrepareOptionsMenu(OCFileListFragment.java'] + }, + + 'WordPress': { + '#6530': ['org.wordpress.android.fluxc.store.PostStore.onAction(PostStore.java'], + '#7182': [ + 'org.wordpress.android.login.LoginUsernamePasswordFragment.onSiteChanged(LoginUsernamePasswordFragment.java'], + '#8659': [ + 'Two different ViewHolders have the same stable ID. Stable IDs in your adapter MUST BE unique and SHOULD NOT change.'], + '#10302': [ + 'org.wordpress.android.login.LoginBaseFormFragment.onOptionsItemSelected(LoginBaseFormFragment.java'], + + '#10363': ['java.lang.IllegalStateException: itemView.findViewById(R.id.container) must not be null', + 'org.wordpress.android.ui.posts.PostListItemViewHolder.(PostListItemViewHolder.kt', + 'org.wordpress.android.ui.posts.PostListItemViewHolder$Compact.(PostListItemViewHolder.kt', + 'org.wordpress.android.ui.posts.adapters.PostListAdapter.onCreateViewHolder(PostListAdapter.kt' + ], + + '#10547': [ + 'start activity ComponentInfo{org.wordpress.android/org.wordpress.android.ui.posts.EditPostActivity}: java.lang.IllegalArgumentException: PostLoadingState wrong value 6', + 'org.wordpress.android.ui.posts.EditPostActivity$PostLoadingState.fromInt(EditPostActivity.java', + 'org.wordpress.android.ui.posts.EditPostActivity.onCreate(EditPostActivity.java' + ], + + '#10876': ['in WithSelect(WithDispatch(WithViewportMatch(WithPreferredColorScheme(Component))))'], + + '#11135': [ + 'java.lang.IllegalStateException: siteStore.getSiteBySiteI…t.getLong(EXTRA_SITE_ID)) must not be null', + 'org.wordpress.android.ui.CommentFullScreenDialogFragment.onCreateView(CommentFullScreenDialogFragment.kt'], + + '#11992': [ + "java.lang.NullPointerException: Attempt to invoke virtual method 'boolean org.wordpress.android.ui.FilteredRecyclerView.isRefreshing()' on a null object reference", + 'org.wordpress.android.ui.reader.ReaderPostListFragment.onSaveInstanceState(ReaderPostListFragment.java'] + } +} + + +def get_testing_result_dir(all_testing_result_dirs, app_name, issue_id): + tmp_paths = [] + for result_dir_path in all_testing_result_dirs: + result_dir_basename = os.path.basename(result_dir_path) + if app_name in result_dir_basename and issue_id in result_dir_basename: + tmp_paths.append(result_dir_path) + return tmp_paths + + +def get_app_name(testing_result_dir): + for app_name in ALL_APPS: + if os.path.basename(testing_result_dir).startswith(app_name): + return app_name + print("Warning: cannot find app name for this testing result dir: %s" % testing_result_dir) + + +def get_apk_info(testing_result_dir: str, app_name: str): + base_name = os.path.basename(testing_result_dir) + target_apk_file_name = str(base_name.split(".apk")[0]) + ".apk" + + target_apk_file_path = os.path.join("../" + app_name, target_apk_file_name) + get_app_package_name_cmd = "aapt dump badging " + target_apk_file_path + " | grep package | awk '{print $2}' | sed s/name=//g | sed s/\\'//g" + app_package_name = "" + + try: + p = subprocess.Popen(get_app_package_name_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + # clear the output + app_package_name = p.communicate()[0].decode('utf-8').strip() + print(app_package_name) + except os.error as e: + print(e) + + return target_apk_file_name, app_package_name + + +def main(args: Namespace): + # collect all testing result dirs + all_testing_results_dirs = [] + subdirs = os.listdir(args.o) + for subdir in subdirs: + # print(subdir) + subdir_path = os.path.join(args.o, subdir) + if os.path.isdir(subdir_path): + all_testing_results_dirs.append(subdir_path) + + # print(all_testing_results_dirs) + # the dict only used for collecting non-target crashes + # key: the apk file name + # value: list of signatures of crash stacks + other_crashes_signature_str_dict: Dict[str, List[str]] = {} + other_crashes_complete_exception_trace_dict: Dict[str, List[List[str]]] = {} + + # check crash + for app_name in app_crash_data: + + if args.app_name is not None and args.app_name != app_name: + # skip unrelated apps if args.app_name is given + continue + + # if args.monkey and app_name == "ActivityDiary": + # # TODO special check for Monkey's ActivityDiary [should be removed in the future] + # continue + + print(args.app_name) + + issue_crash_data = app_crash_data[app_name] + + for issue_id in issue_crash_data: + + if args.issue_id is not None and args.issue_id != issue_id: + # skip unrelated issues if args.issue_id is given + continue + + issue_testing_result_dirs = get_testing_result_dir(all_testing_results_dirs, + app_name, + issue_id) + + if len(issue_testing_result_dirs) == 0: + continue + + log_tag_name = '[' + app_name + ', ' + issue_id + '] ' + + print("\n\n=========") + + crash_signature_strs = issue_crash_data[issue_id] + + # scanning the testing results of the given issue + for result_dir in issue_testing_result_dirs: + + logcat_file_path = "" + testing_time_file_path = "" + login_file_path = "" + + if args.monkey: + # Monkey + logcat_file_path = os.path.join(result_dir, "logcat.log") + login_file_path = os.path.join(result_dir, "login.log") + testing_time_file_path = os.path.join(result_dir, + "monkey_testing_time_on_emulator.txt") + testing_time_datetime_str = '%Y-%m-%d-%H:%M:%S' + + if args.ape: + # Ape + logcat_file_path = os.path.join(result_dir, "logcat.log") + login_file_path = os.path.join(result_dir, "login.log") + testing_time_file_path = os.path.join(result_dir, + "ape_testing_time_on_emulator.txt") + testing_time_datetime_str = '%Y-%m-%d-%H:%M:%S' + + if args.combo: + # ComboDroid + logcat_file_path = os.path.join(result_dir, "logcat.log") + login_file_path = os.path.join(result_dir, "login.log") + testing_time_file_path = os.path.join(result_dir, + "combo_testing_time_on_emulator.txt") + testing_time_datetime_str = '%Y-%m-%d-%H-%M-%S' + + if args.timemachine: + # TimeMachine + logcat_file_path = os.path.join(result_dir, "timemachine-output/crashes.log") + login_file_path = os.path.join(result_dir, "timemachine-run.log") + testing_time_file_path = os.path.join(result_dir, + "timemachine-output/run_time.log") + testing_time_datetime_str = '%Y-%m-%d-%H:%M:%S' + + if args.humanoid: + # Humanoid + logcat_file_path = os.path.join(result_dir, "logcat.log") + login_file_path = os.path.join(result_dir, "login.log") + testing_time_file_path = os.path.join(result_dir, + "humanoid_testing_time_on_emulator.txt") + testing_time_datetime_str = '%Y-%m-%d-%H:%M:%S' + + if args.sapienz: + # Sapienz + logcat_file_path = os.path.join(result_dir, "logcat.log") + login_file_path = os.path.join(result_dir, "login.log") + testing_time_file_path = os.path.join(result_dir, + "sapienz_testing_time_on_emulator.txt") + testing_time_datetime_str = '%Y-%m-%d-%H:%M:%S' + + if args.qtesting: + # Q-testing + logcat_file_path = os.path.join(result_dir, "logcat.log") + login_file_path = os.path.join(result_dir, "login.log") + testing_time_file_path = os.path.join(result_dir, + "qtesting_testing_time_on_emulator.txt") + testing_time_datetime_str = '%Y-%m-%d-%H:%M:%S' + + if args.weighted: + # Q-testing + logcat_file_path = os.path.join(result_dir, "logcat.log") + login_file_path = os.path.join(result_dir, "login.log") + testing_time_file_path = os.path.join(result_dir, + "weighted_testing_time_on_emulator.txt") + testing_time_datetime_str = '%Y-%m-%d-%H:%M:%S' + + if args.fastbot: + # Fastbot + logcat_file_path = os.path.join(result_dir, "logcat.log") + login_file_path = os.path.join(result_dir, "login.log") + testing_time_file_path = os.path.join(result_dir, + "fastbot_testing_time_on_emulator.txt") + testing_time_datetime_str = '%Y-%m-%d-%H:%M:%S' + + if args.wetest: + # WeTest + logcat_file_path = os.path.join(result_dir, "logcat.log") + login_file_path = os.path.join(result_dir, "login.log") + testing_time_file_path = os.path.join(result_dir, + "wetest_testing_time_on_emulator.txt") + testing_time_datetime_str = '%Y-%m-%d-%H:%M:%S' + + if args.newmonkey: + # Newmonkey + logcat_file_path = os.path.join(result_dir, "logcat.log") + login_file_path = os.path.join(result_dir, "login.log") + testing_time_file_path = os.path.join(result_dir, + "newmonkey_testing_time_on_emulator.txt") + testing_time_datetime_str = '%Y-%m-%d-%H:%M:%S' + + if os.path.exists(logcat_file_path) and os.path.exists(testing_time_file_path): + + print('\n') + print(log_tag_name + 'scanning (%s) ' % os.path.basename(result_dir)) + + testing_time_file = open(testing_time_file_path, 'r') + lines = testing_time_file.readlines() + for line in lines: + print(log_tag_name + 'testing time: %s ' % line.strip()) + if len(lines) == 0: + # double check on the file content on the testing time + print(log_tag_name + 'this run does not have recorded testing time, skip this run!') + continue + + time.sleep(1) + + # get the start testing datetime + if args.timemachine: + # special handle timemachine + start_testing_datetime_str = lines[0][0:19] + else: + start_testing_datetime_str = lines[0].strip() + print("the start testing time is: %s" % start_testing_datetime_str) + start_testing_datetime_obj = datetime.datetime.strptime(start_testing_datetime_str, + testing_time_datetime_str) + print("the start testing time (parsed) is: %s" % start_testing_datetime_obj) + + if os.path.exists(login_file_path): + cmd = 'grep ' + "\"" + "Login SUCCESS" + "\" " + login_file_path + p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + # clear the output + output = p.communicate()[0].decode('utf-8').strip() + if len(output) != 0: + print(log_tag_name + "Login SUCCESS") + else: + print(log_tag_name + "Login FAIL?") + + # split the logcat file into separate stack traces + crash_stack_traces: Dict[str, List[str]] = {} + logcat_file = open(logcat_file_path, 'r') + + if args.timemachine: + + if not args.other_crashes: + # special handle for TimeMachine (for target crash) + cache_list = [] + for line in logcat_file.readlines(): + if line.startswith("---"): + continue + + if line.startswith("["): + time_label = line.strip() + crash_stack_traces[time_label] = cache_list + cache_list = [] + else: + cache_list.append(line) + else: + # special handle for TimeMachine (for non-target crash) + fake_time_label_index = 1 + fake_time_label = "fake_time_" + for line in logcat_file.readlines(): + if line.startswith("---") or line.startswith("["): + fake_time_label_index += 1 + fake_time_label = fake_time_label + str(fake_time_label_index) + continue + + if fake_time_label not in crash_stack_traces: + crash_stack_traces[fake_time_label] = [line] + else: + crash_stack_traces[fake_time_label].append(line) + + else: + for line in logcat_file.readlines(): + if line.startswith("---"): + continue + + res = [i for i in range(len(line)) if line.startswith(':', i)] + try: + # hot fix + time_label = line[0:res[2]] # the third ":" is the split point + except IndexError: + print("Catch IndexError when paring logcat!") + continue + if time_label not in crash_stack_traces: + crash_stack_traces[time_label] = [line] + else: + crash_stack_traces[time_label].append(line) + logcat_file.close() + + if args.other_crashes: + + # check the number of other crashes that were detected by-product + app_dir_name = get_app_name(result_dir) + apk_file_name, app_package_name = get_apk_info(result_dir, app_dir_name) + + print("apk file name: %s" % apk_file_name) + print("apk package name: %s" % app_package_name) + + if app_package_name.endswith(".debug") or app_package_name.endswith(".debug.ting"): + app_package_name = app_package_name.split(".debug")[0] + if app_package_name.endswith(".client"): + app_package_name = "com.owncloud.android" + if app_package_name.endswith(".activity"): + app_package_name = app_package_name.split(".activity")[0] + if app_package_name.endswith(".quicknote"): + app_package_name = "com.maubis.scarlet" + if app_package_name.endswith(".attendee"): + app_package_name = "org.fossasia.openevent" + + for time_label in crash_stack_traces: + + if args.timemachine: + # Special handling on timemachine + + target_stack = crash_stack_traces[time_label] + + # split the target_stack into sub exception traces + sub_exception_stacks: Dict[str, List[str]] = {} + for line in target_stack: + res = [i for i in range(len(line)) if line.startswith(':', i)] + print(line) + if len(res) < 3: + # special handling for the case like + # "AndroidRuntime: at android.os.BinderProxy.transactNative(Native Method)" + continue + else: + local_time_label = line[0:res[2]] # the third ":" is the split point + if local_time_label not in sub_exception_stacks: + sub_exception_stacks[local_time_label] = [line] + else: + sub_exception_stacks[local_time_label].append(line) + + for local_time_label in sub_exception_stacks: + if "AndroidRuntime" in local_time_label or "ACRA" in local_time_label \ + or "CustomActivityOnCrash" in local_time_label \ + or "CrashAnrDetector" in local_time_label: + # focus on "AndroidRuntime" bugs + pass + else: + continue + + uniqe_signature_of_crash_stack = "" + + sub_exception_stack = sub_exception_stacks[local_time_label] + for line in sub_exception_stack: + if "at " in line and app_package_name in line: + line_without_time_label = line.replace(local_time_label + ":", "").strip() + uniqe_signature_of_crash_stack += line_without_time_label + + if uniqe_signature_of_crash_stack == "": + continue + + print("\n-- signature --") + print(uniqe_signature_of_crash_stack) + print("----") + + if apk_file_name not in other_crashes_signature_str_dict: + other_crashes_signature_str_dict[apk_file_name] = [ + uniqe_signature_of_crash_stack] + other_crashes_complete_exception_trace_dict[apk_file_name] = [ + sub_exception_stack] + else: + if uniqe_signature_of_crash_stack not in other_crashes_signature_str_dict[ + apk_file_name]: + # check existence + other_crashes_signature_str_dict[apk_file_name].append( + uniqe_signature_of_crash_stack) + other_crashes_complete_exception_trace_dict[apk_file_name].append( + sub_exception_stack) + + else: + + target_stack = crash_stack_traces[time_label] + uniqe_signature_of_crash_stack = "" + + if "AndroidRuntime" in time_label or "ACRA" in time_label \ + or "CustomActivityOnCrash" in time_label \ + or "CrashAnrDetector" in time_label: + # focus on "AndroidRuntime" bugs + pass + else: + continue + + for line in target_stack: + + if "at " in line and app_package_name in line: + line_without_time_label = line.replace(time_label + ":", "").strip() + uniqe_signature_of_crash_stack += line_without_time_label + + if uniqe_signature_of_crash_stack == "": + continue + + print("\n-- signature --") + print(uniqe_signature_of_crash_stack) + print("----") + + if apk_file_name not in other_crashes_signature_str_dict: + other_crashes_signature_str_dict[apk_file_name] = [uniqe_signature_of_crash_stack] + other_crashes_complete_exception_trace_dict[apk_file_name] = [target_stack] + else: + if uniqe_signature_of_crash_stack not in other_crashes_signature_str_dict[ + apk_file_name]: + # check existence + other_crashes_signature_str_dict[apk_file_name].append( + uniqe_signature_of_crash_stack) + other_crashes_complete_exception_trace_dict[apk_file_name].append(target_stack) + + # print("--") + # print(len(set(other_crashes_dict[app_name]))) + # print(other_crashes_dict[app_name]) + # print("--") + + if apk_file_name in other_crashes_signature_str_dict: + number_of_unique_other_crashes = len(set(other_crashes_signature_str_dict[apk_file_name])) + print("#unique other crashes: %d" % number_of_unique_other_crashes) + else: + number_of_unique_other_crashes = 0 + + # output to final result file + if args.final_result_csv_file_path is not None: + with open(args.final_result_csv_file_path, "a") as csv_file: + writer = csv.writer(csv_file) + writer.writerow([apk_file_name, issue_id, os.path.basename(result_dir), + number_of_unique_other_crashes]) + csv_file.close() + + else: + # check the existence of target crashes (i.e., the critical crash we concern) + number_of_matched_crash = 0 + crash_triggering_time_durations = [] + for time_label in crash_stack_traces: + target_stack = crash_stack_traces[time_label] + + is_matched = True + for signature_str in crash_signature_strs: + + exist = False + for line in target_stack: + if signature_str in line: + exist = True + break + if exist: + continue + else: + is_matched = False + break + + if is_matched: + + # compute the time duration to trigger the crash + if args.timemachine: + # print("time label: %s" % time_label) + matched_datetime_str = time_label.replace("[", "").replace("]", "") + crash_triggering_datetime_obj = datetime.datetime.strptime(matched_datetime_str, + '%Y-%m-%d-%H:%M:%S') + else: + matched_datetime_str = "2020-" + time_label.split('.')[0] + crash_triggering_datetime_obj = datetime.datetime.strptime(matched_datetime_str, + '%Y-%m-%d %H:%M:%S') + + tmp_time_duration_in_minutes = ( + crash_triggering_datetime_obj - start_testing_datetime_obj).total_seconds() / 60 + crash_triggering_time_durations.append("{:.0f}".format(tmp_time_duration_in_minutes)) + + number_of_matched_crash += 1 + + if number_of_matched_crash > 0: + print(log_tag_name + "the crash was triggered (%d) times" % number_of_matched_crash) + print(log_tag_name + "the time duration: %s (mins)" % crash_triggering_time_durations) + # if args.v: + # print('---') + # # verbose mode + # print(output) + # print('---') + + # output to final result file + if args.final_result_csv_file_path is not None: + + if not args.simple_format: + with open(args.final_result_csv_file_path, "a") as csv_file: + writer = csv.writer(csv_file) + writer.writerow([app_name, issue_id, os.path.basename(result_dir), + len(crash_triggering_time_durations)]) + for time_duration in crash_triggering_time_durations: + writer.writerow(["", "", "", time_duration]) + csv_file.close() + else: + + if len(crash_triggering_time_durations) > 0: + # output in a simple format: only output triggered bugs and its first triggering time + with open(args.final_result_csv_file_path, "a") as csv_file: + writer = csv.writer(csv_file) + writer.writerow([app_name, issue_id, os.path.basename(result_dir), + len(crash_triggering_time_durations), + crash_triggering_time_durations[0]]) + csv_file.close() + + else: + print('\n') + print(log_tag_name + 'scanning (%s) ' % os.path.basename(result_dir)) + print(log_tag_name + "Warning: logcat file or testing time file in (%s) does not exist!" % + os.path.basename(result_dir)) + continue + + if args.other_crashes: + + total_number_of_other_crashes = 0 + for apk_file_name in other_crashes_signature_str_dict: + print("%s: %d" % (apk_file_name, len(set(other_crashes_signature_str_dict[apk_file_name])))) + total_number_of_other_crashes += len(set(other_crashes_signature_str_dict[apk_file_name])) + + print("#total other crashes: %d" % total_number_of_other_crashes) + + # output the complete stack traces of all the other crashes (i.e., non-target crashes) + tmp_output_dir = os.path.join(args.o, "other_crashes") + if os.path.exists(tmp_output_dir): + shutil.rmtree(tmp_output_dir) + + for apk_file_name in other_crashes_complete_exception_trace_dict: + print("dump complete crash stack for: %s" % apk_file_name) + tmp_output_dir_of_each_apk = os.path.join(tmp_output_dir, apk_file_name) + if not os.path.exists(tmp_output_dir_of_each_apk): + os.makedirs(tmp_output_dir_of_each_apk) + stack_traces: List[List[str]] = other_crashes_complete_exception_trace_dict[apk_file_name] + file_index = 0 + for stack_trace in stack_traces: + tmp_file_path = os.path.join(tmp_output_dir_of_each_apk, "crash_stack_" + str(file_index) + ".txt") + file_index += 1 + with open(tmp_file_path, "w") as tmp_file: + for line in stack_trace: + tmp_file.write(line) + tmp_file.close() + + +if __name__ == '__main__': + + ap = ArgumentParser() + + ap.add_argument('-o', required=True, help="the output directory of testing results") + ap.add_argument('-v', default=False, action='store_true') + + # supported fuzzing tools + ap.add_argument('--monkey', default=False, action='store_true') + ap.add_argument('--ape', default=False, action='store_true') + ap.add_argument('--timemachine', default=False, action='store_true') + ap.add_argument('--combo', default=False, action='store_true') + ap.add_argument('--humanoid', default=False, action='store_true') + ap.add_argument('--sapienz', default=False, action='store_true') + ap.add_argument('--qtesting', default=False, action='store_true') + ap.add_argument('--weighted', default=False, action='store_true') + ap.add_argument('--fastbot', default=False, action='store_true') + ap.add_argument('--wetest', default=False, action='store_true') + ap.add_argument('--newmonkey', default=False, action='store_true') + + ap.add_argument('--app', type=str, dest='app_name') + ap.add_argument('--id', type=str, dest='issue_id') + ap.add_argument('--csv', type=str, dest='final_result_csv_file_path') + ap.add_argument('--simple', default=False, action='store_true', dest='simple_format', + help="standard output in a simple format") + ap.add_argument('--other_crashes', default=False, action='store_true', dest='other_crashes') + + args = ap.parse_args() + + if not os.path.exists(args.o): + ap.error("Error: the output directory does not exist!") + + if args.final_result_csv_file_path is not None and os.path.exists(args.final_result_csv_file_path): + os.remove(args.final_result_csv_file_path) + + main(args)