home/scripts/check_crash_newmonkey.py

842 lines
46 KiB
Python

# 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<org.mozilla.rocket.home.HomeViewModel> 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.<init>(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.<init>(PostListItemViewHolder.kt',
'org.wordpress.android.ui.posts.PostListItemViewHolder$Compact.<init>(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)