예제 #1
0
    def get_coverage(
        self,
        device: Device,
        coverage_folder_local_path: str,
        accumulated_output: str,
        accumulated_errors: str,
    ) -> int:
        # get the unique ids of the methods listed in all the coverage.dat files
        output, errors, result_code = run_cmd(
            f"cat {coverage_folder_local_path}/coverage.dat* | grep -v \"#\" | sort -u | wc -l"
        )
        accumulated_output += output
        accumulated_errors += errors

        number_of_methods_covered = float(output.strip().rstrip('\n'))

        output, errors, result_code = run_cmd(
            f"cat {coverage_folder_local_path}/covids | wc -l")
        accumulated_output += output
        accumulated_errors += errors

        total_number_of_methods = float(output.strip().rstrip('\n'))

        method_coverage = int(number_of_methods_covered /
                              total_number_of_methods * 100)

        return method_coverage
예제 #2
0
    def boot_emulators(self, wait_to_be_ready: bool = False) -> None:
        while True:
            self.next_available_emulator_port = 5554
            self.next_available_adb_server_port = 5038
            self.devices = []
            logger.log_progress(
                f"\nBooting devices: {str(0)}/{str(self.emulators_number)}")

            for i in range(0, self.emulators_number):
                logger.log_progress(
                    f"\nBooting devices: {str(i + 1)}/{str(self.emulators_number)}"
                )
                emulator = Emulator(self)
                emulator.boot()
                self.devices.append(emulator)

            if wait_to_be_ready:
                try:
                    self.wait_devices_to_be_ready()
                except WaitDevicesTimeout as e:
                    logger.log_progress(f"\n{str(e)}")
                    logger.log_progress(
                        "\nForce kill on all current emulator processes")
                    run_cmd("pgrep emu | xargs kill -9", discard_output=True)
                    time.sleep(5)
                    continue

            break
예제 #3
0
    def parse_adb_devices(self, adb_port: int, emulators_found: int,
                          real_devices_found: int) -> Tuple[int, int]:
        devices_cmd = adb.adb_cmd_prefix + ' devices'

        try:
            output, errors, result_code = run_cmd(
                devices_cmd, env={"ANDROID_ADB_SERVER_PORT": str(adb_port)})
        except TimeoutExpired as e:
            return emulators_found, real_devices_found

        error_lines = errors.split("\n")
        for line in error_lines:
            if "daemon not running" in line:
                continue

            if "daemon started successfully" in line:
                continue

            if line.strip() != "":
                raise Exception(
                    f"There was an error running \'adb devices\' command: {errors}"
                )

        lines = output.split("\n")
        for line in lines:
            if "List of devices attached" in line:
                continue

            if line.strip() == "":
                continue

            if "offline" not in line:
                device_name = line.split("\t")[0].strip()

                matching_devices = [
                    device for device in self.devices
                    if device.name == device_name
                ]

                if len(matching_devices) > 0:
                    device = matching_devices.pop(0)
                    if device.state < State.reachable:
                        device.state = State.reachable

                    if type(device) is Emulator:
                        emulators_found += 1
                    else:
                        real_devices_found += 1

                elif "emulator" in line and emulators_found < self.emulators_number:
                    self.devices.append(
                        Emulator(self, device_name, state=State.reachable))
                    emulators_found += 1

                elif "device" in line and real_devices_found < self.real_devices_number:
                    self.devices.append(
                        Device(self, device_name, state=State.reachable))
                    real_devices_found += 1

        return emulators_found, real_devices_found
예제 #4
0
def adb_command(device: 'Device',
                command: str,
                timeout: Optional[int] = None,
                retry: int = 1,
                discard_output: bool = False) -> RunCmdResult:

    cmd = f"{adb_cmd_prefix} -s {device.name} {command}"

    tries = 0
    while True:
        tries += 1
        log_adb_command(device, cmd)

        try:
            output, errors, result_code = run_cmd(
                cmd,
                timeout=timeout,
                discard_output=discard_output,
                env={"ANDROID_ADB_SERVER_PORT": str(device.adb_port)})

            if tries >= retry or result_code == 0:
                return output, errors, result_code

            time.sleep(2)

        except TimeoutExpired as e:

            if tries >= retry:
                return e.stdout, e.stderr, 124

            restart_server(device.adb_port)
            time.sleep(5)
    def instrument(self) -> None:
        self.app_path: str = RequiredFeature('app_path').request()
        self.result_dir: str = RequiredFeature('result_dir').request()
        self.instrumented_subjects_path: str = RequiredFeature('instrumented_subjects_path').request()
        self.emma_instrument_path: str = RequiredFeature('emma_instrument_path').request()

        # first, check if we should assume apps are already instrumented
        assume_subjects_instrumented = RequiredFeature('assume_subjects_instrumented').request()
        if assume_subjects_instrumented:
            features.provide('instrumented_app_path', self.app_path)

            output, errors, result_code = run_cmd(f"aapt dump badging {self.app_path} | grep package:\\ name")
            package_name = output.split("package: name=\'")[1].split("\'")[0]
            features.provide('package_name', package_name)

            self.prepare_files_for_coverage()
            return

        logger.log_progress(f"\nInstrumenting app: {os.path.basename(self.app_path)}")

        # copy sources and instrument application
        instrumented_app_path, package_name = self.prepare_app_for_instrumentation()

        features.provide('package_name', package_name)
        features.provide('instrumented_app_path', instrumented_app_path)

        self.instrument_gradle_file(instrumented_app_path, package_name)

        result_code = os.system(f"./gradlew assembleDebug 2>&1 >{self.result_dir}/build.log")
        if result_code != 0:
            raise Exception("Unable run assembleDebug")

        os.chdir(settings.WORKING_DIR)

        self.prepare_files_for_coverage()
예제 #6
0
    def dump_script_coverage(self, device: Device,
                             coverage_folder_local_path: str,
                             accumulated_output: str, accumulated_errors: str,
                             script_path: str, generation: int,
                             individual_index: int, test_case_index: int,
                             unique_crashes: Set[str],
                             scripts_crash_status: Dict[str, bool]) -> bool:
        result_dir: str = RequiredFeature('result_dir').request()

        if crash_handler.handle(device, script_path, generation,
                                individual_index, test_case_index,
                                unique_crashes):
            scripts_crash_status[script_path] = True
            return False
        else:
            # no crash, collect coverage
            scripts_crash_status[script_path] = False

            adb.log_evaluation_result(device, result_dir, script_path, True)

            # save the coverage.dat file of this test script
            output, errors, result_code = run_cmd(
                f"mv {self.get_coverage_dat_path(device)} {coverage_folder_local_path}/"
            )
            if result_code != 0:
                raise Exception(
                    f"Unable to move the coverage dat file for test script, path is: {self.get_coverage_dat_path(device)}"
                )

            return True
예제 #7
0
def get_current_activity(device: 'Device') -> str:
    cmd = f"{get_adb_cmd_prefix_for_device(device)} shell dumpsys activity activities | grep Activities | head -n 1"
    log_adb_command(device, cmd)

    res = run_cmd(cmd)[0].split("ActivityRecord")[1].split(" ")[2].split(
        "/")[1]
    return res
    def alter_MainActivity(self, manifest_path, main_activity) -> None:
        """
        Take care of changing MainActivity if it is written in Kotlin and is not marked as open.

        :param source_root:
        :param main_activity:
        :return:
        """
        manifest_folder = os.path.dirname(manifest_path)
        main_acivity_name = main_activity.split(".")[-1]
        output, errors, result_code = run_cmd(
            f"find -L {manifest_folder} -name {main_acivity_name}.*")

        main_activity_path = output.strip("\n")
        if ".kt" not in main_activity_path:
            # nothing to do here
            return

        content = ""

        in_stream = open(main_activity_path)

        for index, line in enumerate(in_stream):
            if line.find(f"class {main_acivity_name}") != -1 and line.find(
                    f"open ") == -1:
                # append "open" modifier at begging of line
                content += f"open {line}"
            else:
                content += line

        in_stream.close()
        os.remove(main_activity_path)
        new_file = open(main_activity_path, "w")
        new_file.write(content)
        new_file.close()
예제 #9
0
    def stop_ella(self, device: Device) -> None:
        # stop ELLA server
        output, errors, result_code = run_cmd(f"./ella.sh k", cwd=self.get_device_ella_folder_path(device), timeout=300)
        if result_code != 0:
            raise Exception("Unable to stop ELLA server")

        # wait a bit for ELLA server to stop (it is not quite immediate)
        time.sleep(3)
예제 #10
0
    def extract_coverage(self, html_path: str) -> int:
        xpath_missed_lines = "html/body/table/tfoot/tr/td[8]/text()"
        xpath_missed_lines_cmd = f"xmllint --html -xpath \"{xpath_missed_lines}\" {html_path}"
        output, errors, result_code = run_cmd(xpath_missed_lines_cmd)
        missed_lines_str = output.strip("\n")

        xpath_total_lines = "html/body/table/tfoot/tr/td[9]/text()"
        xpath_total_lines_cmd = f"xmllint --html -xpath \"{xpath_total_lines}\" {html_path}"
        output, errors, result_code = run_cmd(xpath_total_lines_cmd)
        total_lines_str = output.strip("\n")

        missed_lines = float(missed_lines_str.replace(",", ""))
        total_lines = float(total_lines_str.replace(",", ""))
        covered_lines = total_lines - missed_lines

        coverage = int(covered_lines / total_lines * 100)
        return coverage
예제 #11
0
def pkill(device: 'Device', string: str) -> int:
    adb_cmd = f"{get_adb_cmd_prefix_for_device(device)} shell "
    pkill_cmd = adb_cmd + "ps | grep " + string + " | awk '{print $2}' | xargs -I pid " + adb_cmd + "kill pid "

    log_adb_command(device, pkill_cmd)
    try:
        output, errors, result_code = run_cmd(pkill_cmd)
        return result_code
    except TimeoutExpired as e:
        return 124
예제 #12
0
    def instrument_device(self, device: Device) -> bool:
        # build the TCP relay Android app
        output, errors, result_code = run_cmd(f"./gradlew assembleDebug",
                                              cwd=self.tcp_relay_android)
        if result_code != 0:
            raise Exception("Unable build the TCP relay Android app")

        output, errors, result_code = run_cmd(
            f"find {self.tcp_relay_android} -type f -name \"*apk\"")
        if result_code != 0:
            raise Exception(
                "Unable find the APK for the TCP relay Android app")

        tcp_relay_apk_path = output.rstrip('\n')

        adb.uninstall(device, self.tcp_relay_android_package_name)
        adb.install(device, self.tcp_relay_android_package_name,
                    tcp_relay_apk_path)

        return True
    def instrument(self) -> None:
        self.app_path: str = RequiredFeature('app_path').request()
        self.result_dir: str = RequiredFeature('result_dir').request()
        self.instrumented_subjects_path: str = RequiredFeature(
            'instrumented_subjects_path').request()
        self.emma_instrument_path: str = RequiredFeature(
            'emma_instrument_path').request()

        # first, check if we should assume apps are already instrumented
        assume_subjects_instrumented = RequiredFeature(
            'assume_subjects_instrumented').request()
        if assume_subjects_instrumented:
            features.provide('instrumented_app_path', self.app_path)

            # copy emma generated file
            result_code = os.system(
                f"cp bin/coverage.em {self.result_dir}/{logger.redirect_string()}"
            )
            if result_code != 0:
                raise Exception("Unable to copy coverage.em file")

            output, errors, result_code = run_cmd(
                f"aapt dump badging {self.app_path} | grep package:\\ name")
            package_name = output.split("package: name=\'")[1].split("\'")[0]
            features.provide('package_name', package_name)
            features.provide('compiled_package_name', package_name)
            return

        logger.log_progress(
            f"\nInstrumenting app: {os.path.basename(self.app_path)}")

        # copy sources and instrument application
        instrumented_app_path, package_name = self.prepare_app_for_instrumentation(
        )

        # compile with emma data
        result_code = os.system(
            f"ant clean emma debug 2>&1 >{self.result_dir}/build.log")
        if result_code != 0:
            raise Exception("Unable run ant clean emma debug")

        # copy emma generated file
        result_code = os.system(
            f"cp bin/coverage.em {self.result_dir}/{logger.redirect_string()}")
        if result_code != 0:
            raise Exception("Unable to copy coverage.em file")

        os.chdir(settings.WORKING_DIR)

        features.provide('package_name', package_name)
        features.provide('instrumented_app_path', instrumented_app_path)

        # assume same compiled package name as the one declard in AndroidManifest.xml file
        features.provide('compiled_package_name', package_name)
    def instrument_gradle_file(self, instrumented_app_path, package_name):
        build_gradle_path = self.find_build_gradle_path(instrumented_app_path)

        # check which changes need to be made to the build.gradle file
        add_jacoco_plugin = False
        enable_test_coverage = False
        output, errors, result_code = run_cmd(f"cat {build_gradle_path} | grep \"apply plugin: 'jacoco'\"")
        if output.strip() == "":
            add_jacoco_plugin = True

        output, errors, result_code = run_cmd(f"cat {build_gradle_path} | grep \"testCoverageEnabled = true\"")
        if output.strip() == "":
            enable_test_coverage = True

        if add_jacoco_plugin:
            self.add_jacoco_plugin_to_gradle_file(build_gradle_path, package_name)

        if enable_test_coverage:
            self.enable_test_coverage_in_gradle_file(build_gradle_path, package_name)

        self.provide_compiled_package_name(build_gradle_path, package_name)
    def find_build_gradle_path(self, instrumented_app_path):
        find_gradle_path_cmd = f"grep -l -R \"'com.android.application'\" {settings.WORKING_DIR}{instrumented_app_path} "
        find_gradle_path_cmd += "| xargs -I {} grep -L \"com.google.android.support:wearable\" {}"
        find_gradle_path_cmd += "| xargs -I {} grep -L \"com.google.android.wearable:wearable\" {}"
        find_gradle_path_cmd += "| grep \"build.gradle$\""

        output, errors, result_code = run_cmd(find_gradle_path_cmd)
        grep_result = list(filter(lambda p: p != "", output.split("\n")))
        if len(grep_result) != 1:
            raise Exception("Unable to find build.gradle file in instrumented app path")

        return grep_result[0]
예제 #16
0
    def get_application_folder_path(self):
        grep_cmd = f"grep -l -R \"'com.android.application'\" {self.root_project_path()} "
        grep_cmd += "| xargs -I {} grep -L \"com.google.android.support:wearable\" {}"
        grep_cmd += "| xargs -I {} grep -L \"com.google.android.wearable:wearable\" {}"
        grep_cmd += "| grep \"build.gradle$\""

        output, errors, result_code = run_cmd(grep_cmd)
        grep_result = output.strip("\n")

        if grep_result == "":
            raise Exception("Unable to find application path inside project.")

        return str(Path(grep_result).parent) + "/"
    def alter_AndroidManifest(self, path: str, package_name: str) -> None:
        output, errors, result_code = run_cmd(
            f"cat {path} | grep \"emma updated\"")
        if output.strip() != "":
            # we will assume Android Manifest was already modified
            return

        is_mod = False

        content = ""
        in_stream = open(path)
        for index, line in enumerate(in_stream):
            if line.find("</application>") != -1:

                content += \
                    f'''
    
                     <!-- emma updated -->
                    <activity 
                        android:label="EmmaInstrumentationActivity" 
                        android:name="{package_name}.EmmaInstrument.InstrumentedActivity"/>
                    <receiver android:name="{package_name}.EmmaInstrument.CollectCoverageReceiver">
                        <intent-filter>
                            <action android:name="evolutiz.emma.COLLECT_COVERAGE" />
                        </intent-filter>
                    </receiver>
                    <!-- emma updated -->
                {line}
                
                 <!-- emma updated -->
                 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  
                 <instrumentation 
                    android:handleProfiling="true" 
                    android:label="EmmaInstrumentation" 
                    android:name="{package_name}.EmmaInstrument.EmmaInstrumentation" 
                    android:targetPackage="{package_name}"/>
                 <!-- emma updated -->
    
                '''
                is_mod = True
            else:
                content += line

        in_stream.close()
        os.remove(path)
        new_file = open(path, "w")
        new_file.write(content)
        new_file.close()

        if not is_mod:
            print("[Error] Failed when update AndroidManifest.xml")
예제 #18
0
    def start_ella(self, device: Device) -> None:
        self.prepare_ella_folder_for_device(device)

        run_cmd(f"find {self.get_device_ella_output_folder_path(device)} -type f -name \"coverage.dat.*\" | "
                f"xargs -I {{}} rm {{}}")

        # start ELLA server
        output, errors, result_code = run_cmd(f"./ella.sh s", cwd=self.get_device_ella_folder_path(device))
        if result_code != 0:
            raise Exception("Unable to start ELLA server")

        # start the TCP relay service on the device
        output, errors, result_code = adb.shell_command(
            device,
            f"am startservice -n {self.tcp_relay_android_package_name}/.RelayService -a start "
            f"--ei pA {self.ella_original_port} --ei pB {self.get_device_tcp_relay_port(device)}")
        if result_code != 0:
            raise Exception("Unable to start the TCP relay service")

        # forward the TCP relay port from the emulator to the local PC
        device.forward_port(self.get_device_tcp_relay_port(device), self.get_device_tcp_relay_port(device))

        # forward, locally, the TCP relay port to the ELLA server port
        run_cmd(f"socat TCP:localhost:{self.get_device_ella_port(device)} TCP:localhost:{self.get_device_tcp_relay_port(device)} &", discard_output=True)
예제 #19
0
    def get_current_apk_output_folder(self, device: Optional[Device] = None) -> str:
        self.app_path: str = RequiredFeature('app_path').request()

        # find folder in ELLa's output folder that has the APK's path (or its hash if it is too long) in its name
        aux = self.app_path.replace("/", "_")
        if len(aux) > 100:
            aux = sha256(aux.encode()).hexdigest()

        output, errors, result_code = run_cmd(
            f"find -L {self.ella_original_output_folder_path if device is None else self.get_device_ella_output_folder_path(device)} -type d -name \"*{aux}*\"")
        folder = output.rstrip('\n')

        if folder == '':
            raise Exception(f"Unable to find ELLA output folder for app {self.app_path}")

        return folder
예제 #20
0
def compress_results(strategy_with_runner_name: str) -> None:
    app_path = RequiredFeature('app_path').request()
    app_name = os.path.basename(app_path)

    repetitions_dir = f"{settings.WORKING_DIR}results/{strategy_with_runner_name}/{app_name}/"

    output, errors, return_code = run_cmd(
        f"find -L {repetitions_dir} -maxdepth 1 -mindepth 1 -type d")
    repetition_dir_paths = output.split('\n')

    pool = mp.Pool(processes=len(repetition_dir_paths))
    for path in repetition_dir_paths:
        if path.strip() != '':
            pool.apply_async(compress_repetition, args=(path, ))
    pool.close()
    pool.join()
예제 #21
0
def get_imei(device: 'Device') -> Optional[str]:
    if device.name not in devices_imei:
        adb_cmd = f"{get_adb_cmd_prefix_for_device(device)} shell "
        imei_cmd = f"{adb_cmd}dumpsys iphonesubinfo | grep 'Device ID' | cut -d ' ' -f 6 "

        # leave commented to avoid infinite recursion
        # log_adb_command(device, cmd)

        try:
            output, errors, result_code = run_cmd(imei_cmd)
            devices_imei[device.name] = output.strip()

        except TimeoutExpired as e:
            return None

    return devices_imei[device.name]
예제 #22
0
    def generate_test_coverage(self, device: Device,
                               coverage_folder_local_path: str,
                               accumulated_output: str,
                               accumulated_errors: str, script_path: str,
                               generation: int, individual_index: int,
                               test_case_index: int, unique_crashes: Set[str],
                               scripts_crash_status: Dict[str, bool]) -> bool:
        compiled_package_name: str = RequiredFeature(
            'compiled_package_name').request()
        result_dir: str = RequiredFeature('result_dir').request()

        # clear app's data and state
        output, errors, result_code = adb.shell_command(
            device, f"pm clear {compiled_package_name}")
        accumulated_output += str(output)
        accumulated_errors += str(errors)
        if result_code != 0:
            adb.log_evaluation_result(device, result_dir, script_path, False)
            if self.verbose_level > 0:
                logger.log_progress(
                    f"\n{accumulated_output}\n{accumulated_errors}")
            raise Exception(
                f"Unable to clear package for script_path {script_path} in device: {device.name}"
            )

        # copy the covids file to the coverage_folder_local_path
        ella_coverage_ids_file_path = f"{self.get_current_apk_output_folder(device)}/covids"
        output, errors, result_code = run_cmd(
            f"cp {ella_coverage_ids_file_path} {coverage_folder_local_path}/")
        if result_code != 0:
            raise Exception(
                f"Unable to copy the coverage ids file for test script, path is: {ella_coverage_ids_file_path}"
            )

        # Run test case using the requested test runner.
        # Internally, the test runner will take care of calling the methods AppInstrumentator#setup_for_test_run and
        # AppInstrumentator#teardown_after_test_run, which will start and stop the ELLA server correspondingly.
        script_name = script_path.split("/")[-1]
        test_runner = RequiredFeature('test_runner').request()
        test_runner.run(device, compiled_package_name, script_name)

        return self.dump_script_coverage(device, coverage_folder_local_path,
                                         accumulated_output,
                                         accumulated_errors, script_path,
                                         generation, individual_index,
                                         test_case_index, unique_crashes,
                                         scripts_crash_status)
    def get_manifest_path(self, root_path: str) -> str:
        find_manifest_cmd = f"find -L {root_path} -type f -name AndroidManifest.xml | "  # find all manifests
        find_manifest_cmd += "xargs -I {} grep -l \"android.intent.action.MAIN\" {} | "  # which contain a Main Activity
        find_manifest_cmd += "xargs -I {} grep -L wearable {} | "  # and are not a wearable app
        find_manifest_cmd += "grep -v build | grep -v androidTest | grep -v bin"  # also, discard build and test related manifests

        output, errors, result_code = run_cmd(find_manifest_cmd)
        files = list(
            filter(
                lambda p: p != "" and not ("classes" in os.listdir(
                    os.path.dirname(p)) and "/bin/" in p), output.split("\n")))

        if len(files) != 1:
            raise Exception(
                f"Unable to find AndroidManifest.xml file for instrumentation. "
                + f"There seems to be several matches: {', '.join(files)}")
        return files[0]
예제 #24
0
    def get_coverage(
            self,
            device: Device,
            coverage_folder_local_path: str,
            accumulated_output: str,
            accumulated_errors: str,
    ) -> int:
        result_dir: str = RequiredFeature('result_dir').request()
        coverage_ec_local_path = f"{coverage_folder_local_path}/coverage.ec"

        # pull coverage.ec file from device
        jacoco_coverage_class_files_path = RequiredFeature('jacoco_coverage_class_files_path').request()
        output, errors, result_code = adb.pull(device,
                                               self.coverage_ec_device_backup_path,
                                               coverage_ec_local_path)
        accumulated_output += output
        accumulated_errors += errors
        if result_code != 0:
            adb.log_evaluation_result(device, result_dir, "pull-coverage", False)
            if self.verbose_level > 0:
                logger.log_progress(f"\n{accumulated_output}\n{accumulated_errors}")
            raise Exception(f"Unable to pull coverage for device: {device.name}")

        # process coverage.ec file
        app_path = RequiredFeature('app_path').request()
        jacoco_cmd = f"java -jar {settings.WORKING_DIR}lib/jacococli.jar " \
                   f"report coverage.ec " \
                   f"--classfiles {jacoco_coverage_class_files_path} " \
                   f"--sourcefiles {settings.WORKING_DIR}{app_path}/src " \
                   f"--xml jacoco_report.xml " \
                   f"--html jacoco_html_report"

        output, errors, result_code = run_cmd(jacoco_cmd, cwd=coverage_folder_local_path)
        accumulated_output += output
        accumulated_errors += errors
        if result_code != 0:
            adb.log_evaluation_result(device, result_dir, "process-coverage", False)
            if self.verbose_level > 0:
                logger.log_progress(f"\n{accumulated_output}\n{accumulated_errors}")
            raise Exception(f"Unable to process coverage.ec file fetched from device: {device.name}")

        # parse generated html to extract global line coverage
        html_path = f"{coverage_folder_local_path}/jacoco_html_report/index.html"
        return self.extract_coverage(html_path)
예제 #25
0
    def prepare_ella_folder_for_device(self, device: Device):
        if device.name in self.devices_with_ella_folder:
            return

        device_ella_folder_path = self.get_device_ella_folder_path(device)
        device_ella_settings_path = f"{device_ella_folder_path}ella.settings"
        device_ella_port = self.get_device_ella_port(device)

        # copy folder and change port in ELLA settings
        run_cmd(f"rm -rf {device_ella_folder_path}")
        run_cmd(f"cp -r {self.ella_original_folder_path} {device_ella_folder_path}", timeout=300)
        run_cmd(f"sed -i \'s/ella.server.port=23745/ella.server.port={device_ella_port}/g\' {device_ella_settings_path}")

        self.devices_with_ella_folder.add(device.name)
예제 #26
0
def install(device: 'Device', package_name: str, apk_path: str) -> None:
    verbose_level = RequiredFeature('verbose_level').request()

    output, errors, result_code = adb_command(device, f"install {apk_path}")
    if result_code != 0 or 'INSTALL_FAILED' in output:
        error_msg = f"Unable to install apk: {apk_path} on device: {device.name}"
        if verbose_level > 0:
            logger.log_progress("\n" + error_msg)
            logger.log_progress("\n" + output)
            logger.log_progress("\n" + errors)

        raise Exception(error_msg)

    cmd = f"{get_adb_cmd_prefix_for_device(device)} shell pm list packages | grep {package_name}"
    log_adb_command(device, cmd)

    res = run_cmd(cmd)[0].strip()
    if package_name not in res:
        raise Exception(
            f"Unable to install apk: {apk_path} on device: {device.name}")
예제 #27
0
    def get_coverage(
            self,
            device: Device,
            coverage_folder_local_path: str,
            accumulated_output: str,
            accumulated_errors: str,
    ) -> int:
        result_dir: str = RequiredFeature('result_dir').request()
        coverage_ec_local_path = f"{coverage_folder_local_path}/coverage.ec"

        # pull coverage.ec file from device
        output, errors, result_code = adb.pull(device, self.coverage_ec_device_backup_path, coverage_ec_local_path)
        accumulated_output += output
        accumulated_errors += errors
        if result_code != 0:
            adb.log_evaluation_result(device, result_dir, "pull-coverage", False)
            if self.verbose_level > 0:
                logger.log_progress(f"\n{accumulated_output}\n{accumulated_errors}")
            raise Exception(f"Unable to pull coverage for device: {device.name}")

        # process coverage.ec file
        app_path = RequiredFeature('app_path').request()
        emma_cmd = f"java -cp {settings.WORKING_DIR}lib/emma.jar emma report -r html" \
                   f" -in coverage.em,coverage.ec -sp {settings.WORKING_DIR}{app_path}/src"
        output, errors, result_code = run_cmd(emma_cmd, cwd=coverage_folder_local_path)
        accumulated_output += output
        accumulated_errors += errors
        if result_code != 0:
            adb.log_evaluation_result(device, result_dir, "process-coverage", False)
            if self.verbose_level > 0:
                logger.log_progress(f"\n{accumulated_output}\n{accumulated_errors}")
            raise Exception(f"Unable to process coverage.ec file fetched from device: {device.name}")

        # parse generated html to extract global line coverage
        html_path = f"{coverage_folder_local_path}/coverage/index.html"
        coverage_str = self.extract_coverage(html_path)

        aux = coverage_str.split("%")
        coverage = int(aux[0])

        return coverage
예제 #28
0
    def get_apk_path(self) -> None:
        self.apk_path = None
        if self.instrumented_app_path.endswith(".apk"):
            self.apk_path = self.instrumented_app_path
        else:
            # now find its name
            output, errors, result_code = run_cmd(
                f"find -L {self.instrumented_app_path} -name \"*.apk\" | grep -v androidTest | grep -v unaligned"
            )
            apk_paths = []
            for file_path in output.split("\n"):
                if file_path != "":
                    apk_paths.append(file_path)

            if len(apk_paths) == 0:
                raise Exception(
                    f"No APKs found inside folder {self.instrumented_app_path} after build."
                )

            if len(apk_paths) > 1:
                # try to filter APKs based on ETG.config file (might not be present)
                etg_config_path = f"{self.instrumented_app_path}/etg.config"
                if os.path.isfile(etg_config_path):
                    etg_config = ETGConfig(etg_config_path)

                    # try to filter by build type
                    build_type = etg_config.build_type()
                    apk_paths = list(
                        filter(lambda path: f"/{build_type}/" in path,
                               apk_paths))

                    # try to filter by product flavors
                    product_flavors = etg_config.product_flavors()
                    if len(product_flavors) > 0:
                        product_flavors_combined = ''
                        for index, flavor in enumerate(product_flavors):
                            if index == 0:
                                product_flavors_combined += flavor.lower()
                            else:
                                product_flavors_combined += flavor.capitalize()

                        apk_paths = list(
                            filter(
                                lambda path: f"/{product_flavors_combined}/" in
                                path, apk_paths))

                        if len(apk_paths) == 0:
                            raise Exception(
                                f"Evolutiz was unable to determine which APK inside folder "
                                f"{self.instrumented_app_path} should it use, since neither of them satisfy the "
                                f"combined product flavor provided: {product_flavors_combined} in the ETG config "
                                f"file")
                else:
                    # TODO: provide more info about ETG config files
                    raise Exception(
                        f"There are several APKs found inside folder {self.instrumented_app_path} after "
                        f"build. Evolutiz was unable to determine which one should it use. "
                        f"You can help it by providing an ETG config file at the root of the app's folder."
                    )

            self.apk_path = apk_paths[0]

        features.provide('apk_path', self.apk_path)
        assert self.apk_path is not None
예제 #29
0
    def instrument(self) -> None:
        self.app_path: str = RequiredFeature('app_path').request()
        self.result_dir: str = RequiredFeature('result_dir').request()
        self.instrumented_subjects_path: str = RequiredFeature(
            'instrumented_subjects_path').request()

        if not self.app_path.endswith(".apk"):
            cause = "ELLA instrumentation only works with APKs.\n" \
                    f"Provided app path was: {self.app_path}"
            logger.log_progress(cause)
            raise Exception(cause)

        apk_filename = os.path.basename(self.app_path)
        logger.log_progress(f"\nInstrumenting app: {apk_filename}")

        # delete any previous content in ELLA's output folder
        run_cmd(f"rm -rf {self.ella_original_output_folder_path}/*")

        # delete any previous content generated by ELLA in /tmp folder
        run_cmd(f"rm /tmp/ella* /tmp/inputclasses*")

        # prepare the ella-customized jars
        output, errors, result_code = run_cmd(
            f"python gen-ella-wrappers.py",
            cwd=self.ella_original_folder_path,
            timeout=120)
        if result_code != 0:
            raise Exception("Unable generate ELLA wrappers")

        output, errors, result_code = run_cmd(
            f"ant clean build", cwd=self.ella_original_folder_path)
        if result_code != 0:
            raise Exception("Unable compile ELLA JARs")

        # perform the instrumentation of the APK
        output, errors, result_code = run_cmd(
            f"./ella.sh i {self.app_path}",
            cwd=self.ella_original_folder_path,
            timeout=120)
        if result_code != 0:
            raise Exception(
                f"Unable instrument {self.app_path} APK using ELLA")

        os.chdir(settings.WORKING_DIR)

        # find instrumented APK in ELLA's output folder
        output, errors, result_code = run_cmd(
            f"find {self.get_current_apk_output_folder()} -type f -name \"instrumented.apk\""
        )
        instrumented_app_path = output.rstrip('\n')
        features.provide('instrumented_app_path', instrumented_app_path)

        # get package and Main activity name directly from the original APK
        output, errors, result_code = run_cmd(
            f"aapt dump badging {self.app_path} | grep package:\\ name")
        package_name = output.split("package: name=\'")[1].split("\'")[0]
        features.provide('package_name', package_name)
        features.provide('compiled_package_name', package_name)

        output, errors, result_code = run_cmd(
            f"aapt dump badging {self.app_path} | grep launchable-activity | cut -d' ' -f2 | cut -d\"'\" -f 2"
        )
        main_activity_name = output.rstrip('\n')
        features.provide('main_activity', main_activity_name)
예제 #30
0
def get_current_package_name(device: 'Device') -> str:
    cmd = f"{get_adb_cmd_prefix_for_device(device)} shell dumpsys activity recents | grep 'Recent #0' | cut -d= -f2 | sed 's| .*||' | cut -d '/' -f1"
    log_adb_command(device, cmd)

    res = run_cmd(cmd)[0].strip("\n")
    return res