Ejemplo n.º 1
0
class NormalPhasePlugin:
    """Plugin class for pytest"""
    def __init__(self, test_set):
        """Initialize database connection"""
        self.test_func_times = {}
        self.test_set = test_set
        self.database = DatabaseHelper()
        self.database.init_conn()
        self.fill_times_dict()

    def fill_times_dict(self):
        """Query running times of tests from database"""
        for testname in self.test_set:
            self.test_func_times[testname] = self.database.get_test_duration(
                testname)

    def pytest_collection_modifyitems(self, session, config, items):
        """Select only specific tests for running and prioritize them based on queried times"""
        del config
        original_length = len(items)
        selected = []
        for item in items:
            if item.nodeid in self.test_set:
                selected.append(item)

        items[:] = sorted(selected,
                          key=lambda item: self.test_func_times[item.nodeid])

        session.config.hook.pytest_deselected(
            items=([FakeItem(session.config)] *
                   (original_length - len(selected))))
Ejemplo n.º 2
0
def test_newly_added_tests(helper):
    db = DatabaseHelper()
    db.init_conn()

    helper.change_file("changes/test_car/add_test_passengers.txt", "tests/test_car.py")

    new_tests = common.read_newly_added_tests(db)
    db.close_conn()

    assert new_tests == {"tests/test_car.py::test_passengers"}
Ejemplo n.º 3
0
class InitPhasePlugin:
    """Class to handle mapping database initialization"""
    def __init__(self):
        """"Constructor calls database and Coverage.py initialization"""
        self.test_func_lines = {}
        self.cov = coverage.Coverage()
        self.cov._warn_unimported_source = False
        self.testfiles = set()
        self.database = DatabaseHelper()
        self.database.init_conn()
        self.database.init_mapping_db()
        self.head_hash = get_current_head_hash()
        self.database.save_last_update_hash(self.head_hash)

    def pytest_collection_modifyitems(self, session, config, items):
        """Calculate function start and end line numbers from testfiles"""
        del session, config
        for item in items:
            testfile = item.nodeid.split("::")[0]
            self.testfiles.add(testfile)
            if testfile not in self.test_func_lines:
                testfile_src_code = coverage.python.get_python_source(testfile)
                self.test_func_lines[testfile] = calculate_func_lines(
                    testfile_src_code)

    @pytest.hookimpl(hookwrapper=True)
    def pytest_runtest_protocol(self, item, nextitem):
        """Start coverage collection for each test function run and save data"""
        del nextitem
        if isinstance(item, Function):
            start = timer()
            self.cov.erase()
            self.cov.start()
            yield
            self.cov.stop()
            self.cov.save()
            end = timer()
            elapsed = round(end - start, 4)
            _, test_function_id = save_testfile_and_func_data(
                item, elapsed, self.test_func_lines, self.database)
            save_mapping_data(
                test_function_id,
                self.cov.get_data(),
                self.testfiles,
                self.database,
            )
        else:
            yield
Ejemplo n.º 4
0
def test_split_changes():
    db = DatabaseHelper()
    db.init_conn()

    change_list = ["src/car.py", "random_file.txt", "tests/test_car.py"]
    for path in change_list:
        with open(path, "w") as f:
            f.write("nothing")

    test_files, src_files = common.split_changes(change_list, db)
    db.close_conn()

    for t in test_files:
        assert t[1] in {"tests/test_car.py"}

    for s in src_files:
        assert s[1] in {"src/car.py"}
Ejemplo n.º 5
0
def test_tests_from_changed_sourcefiles(helper):
    db = DatabaseHelper()
    db.init_conn()

    helper.change_file("changes/car/change_accelerate.txt", "src/car.py")

    changed_files = ["src/car.py"]
    test_files, src_files = common.split_changes(changed_files, db)
    diff_dict = common.file_diff_dict_current(src_files)

    (
        test_set,
        changed_lines_dict,
        new_line_map_dict,
        files_to_warn,
    ) = common.tests_from_changed_srcfiles(diff_dict, src_files, db)
    db.close_conn()

    assert test_set == {"tests/test_car.py::test_acceleration"}
Ejemplo n.º 6
0
def test_delete_ran_lines():
    conn = sqlite3.connect(DB_FILE_NAME)
    c = conn.cursor()
    file_id = 1
    sql = "SELECT line_id FROM test_map WHERE file_id = ?"
    old_line_ids = [x[0] for x in c.execute(sql, (file_id, )).fetchall()]
    conn.close()

    db = DatabaseHelper()
    db.init_conn()
    db.delete_ran_lines(old_line_ids, file_id)
    db.close_conn()

    conn = sqlite3.connect(DB_FILE_NAME)
    c = conn.cursor()
    new_line_ids = [x[0] for x in c.execute(sql, (file_id, )).fetchall()]
    conn.close()

    assert not new_line_ids
Ejemplo n.º 7
0
def main():
    """Collect newly added tests and store them to database"""
    db_helper = DatabaseHelper()
    db_helper.init_conn()
    existing_tests = set()
    for test in [
        x[0]
        for x in db_helper.db_cursor.execute(
            "SELECT context FROM test_function"
        ).fetchall()
    ]:
        existing_tests.add(test)

    test_set = newly_added_tests(existing_tests)
    db_helper.db_cursor.execute("DELETE FROM new_tests")
    for test in test_set:
        db_helper.db_cursor.execute(
            "INSERT INTO new_tests (context) VALUES (?)", (test,)
        )
    db_helper.db_conn.commit()
    db_helper.db_conn.close()
Ejemplo n.º 8
0
def test_update_db_from_test_mapping():
    # shift all lines and compare old and new lines in db
    conn = sqlite3.connect(DB_FILE_NAME)
    c = conn.cursor()
    testfile_id = 2
    funclines_sql = "SELECT id,start,end FROM test_function WHERE test_file_id = ?"
    filename_sql = "SELECT path FROM test_file WHERE id = ?"

    testfile_name = c.execute(filename_sql, (testfile_id, )).fetchone()[0]
    old_func_line_dict = {
        x[0]: (x[1], x[2])
        for x in c.execute(funclines_sql, (testfile_id, )).fetchall()
    }

    conn.close()

    shift = 5
    line_count = sum(1 for line in open(testfile_name))
    line_map = {x: x + shift for x in range(1, line_count + 1)}

    db = DatabaseHelper()
    db.init_conn()
    db.update_db_from_test_mapping(line_map, testfile_id)
    db.close_conn()

    conn = sqlite3.connect(DB_FILE_NAME)
    c = conn.cursor()
    new_func_line_dict = {
        x[0]: (x[1], x[2])
        for x in c.execute(funclines_sql, (testfile_id, )).fetchall()
    }
    conn.close()

    assert len(old_func_line_dict) == len(new_func_line_dict)
    for key in old_func_line_dict.keys():
        old_func_linenumbers = old_func_line_dict[key]
        new_func_linenumbers = new_func_line_dict[key]
        assert old_func_linenumbers[0] + shift == new_func_linenumbers[0]
        assert old_func_linenumbers[1] + shift == new_func_linenumbers[1]
Ejemplo n.º 9
0
def test_tests_from_changed_testfiles(helper):
    db = DatabaseHelper()
    db.init_conn()

    helper.change_file(
        "changes/test_shop/change_test_normal_shop_purchase.txt",
        "tests/test_shop.py",
    )

    changed_files = [
        "tests/test_shop.py",
    ]
    test_files, src_files = common.split_changes(changed_files, db)
    diff_dict = common.file_diff_dict_current(test_files)

    (
        test_set,
        changed_lines_dict,
        new_line_map_dict,
    ) = common.tests_from_changed_testfiles(diff_dict, test_files, db)
    db.close_conn()

    assert test_set == {"tests/test_shop.py::test_normal_shop_purchase"}
Ejemplo n.º 10
0
def test_update_db_from_src_mapping():
    # shift all lines and compare old and new lines in db
    conn = sqlite3.connect(DB_FILE_NAME)
    c = conn.cursor()
    file_id = 1
    sql = "SELECT line_id FROM test_map WHERE file_id = ?"
    old_line_ids = [x[0] for x in c.execute(sql, (file_id, )).fetchall()]
    conn.close()

    shift = 5
    line_map = {x: x + shift for x in old_line_ids}

    db = DatabaseHelper()
    db.init_conn()
    db.update_db_from_src_mapping(line_map, file_id)
    db.close_conn()

    conn = sqlite3.connect(DB_FILE_NAME)
    c = conn.cursor()
    new_line_ids = [x[0] for x in c.execute(sql, (file_id, )).fetchall()]
    conn.close()

    assert new_line_ids == [k + shift for k in old_line_ids]
Ejemplo n.º 11
0
 def get_newly_added_tests_from_tool(self):
     db = DatabaseHelper()
     db.init_conn()
     new_tests = read_newly_added_tests(db)
     db.close_conn()
     return new_tests
Ejemplo n.º 12
0
 def get_all_tests_for_srcfile(self, src_file_id):
     db = DatabaseHelper()
     db.init_conn()
     all_tests = db.query_all_tests_srcfile(src_file_id)
     db.close_conn()
     return all_tests
Ejemplo n.º 13
0
 def get_tests_from_tool_committed(self):
     db = DatabaseHelper()
     db.init_conn()
     change_data = get_tests_and_data_committed(db)
     db.close_conn()
     return change_data.test_set
Ejemplo n.º 14
0
def random_remove_test(iterations, deletes_per_iteration, max_wait, logger):
    """Delete random lines and evaluate tests sets and pytest exitcodes"""
    if not os.path.isfile(DB_FILE_NAME):
        logger.info("Running mapping database initialization...")
        subprocess.run(["pytest", "--rts"], check=False)

    results_db = ResultDatabase()
    results_db.init_conn()
    results_db.init_results_db()

    mapping_db = DatabaseHelper()
    mapping_db.init_conn()

    test_suite_size = mapping_db.get_test_suite_size()
    project_name = os.getcwd()
    init_hash = mapping_db.get_last_update_hash()
    db_size = os.path.getsize("./mapping.db")
    project_id = results_db.store_results_project(project_name, init_hash,
                                                  test_suite_size, db_size)

    _, src_files = mapping_db.get_testfiles_and_srcfiles()

    testhelper = TestHelper()

    for i in range(iterations):

        # Remove random lines
        testhelper.checkout_new_branch()
        for j in range(deletes_per_iteration):
            random_file = select_random_file(src_files)
            filename = random_file[1]
            delete_random_line(filename)
            testhelper.commit_change(filename, str(j + 1))

        # Gets tests based on line-level and file-level change
        current_git_hash = get_current_head_hash()
        changed_files = changed_files_between_commits(init_hash,
                                                      current_git_hash)
        tests_line_level = set()
        tests_file_level = set()
        for filename in changed_files:
            diff = file_diff_data_between_commits(filename, init_hash,
                                                  current_git_hash)
            test_lines, _, _ = get_test_lines_and_update_lines(diff)
            file_id = mapping_db.save_src_file(filename)
            tests_line = mapping_db.query_tests_srcfile(test_lines, file_id)
            tests_file = mapping_db.query_all_tests_srcfile(file_id)
            for testfunc_line in tests_line:
                tests_line_level.add(testfunc_line)
            for testfunc_file in tests_file:
                tests_file_level.add(testfunc_file)

        # Get full git diff for analysis
        full_diff = full_diff_between_commits(init_hash, current_git_hash)

        # Pytest exitcodes for running different test sets
        exitcode_line = (capture_specific_exit_code(
            list(tests_line_level), max_wait) if tests_line_level else 5)
        exitcode_file = (capture_specific_exit_code(
            list(tests_file_level), max_wait) if tests_file_level else 5)
        exitcode_all = capture_all_exit_code(max_wait)

        # Clear removal
        testhelper.checkout_branch("master")
        testhelper.delete_branch("new-branch")

        # Store and print data
        results_db.store_results_data(
            project_id,
            deletes_per_iteration,
            exitcode_line,
            exitcode_file,
            exitcode_all,
            len(tests_line_level),
            len(tests_file_level),
            full_diff,
        )
        print_remove_test_output(
            i,
            project_name,
            init_hash,
            deletes_per_iteration,
            test_suite_size,
            len(tests_line_level),
            len(tests_file_level),
            exitcode_line,
            exitcode_file,
            exitcode_all,
            RESULTS_DB_FILE_NAME,
            logger,
        )
Ejemplo n.º 15
0
def pytest_configure(config):
    """Register RTS plugins based on state"""
    logger = logging.getLogger()
    logging.basicConfig(format="%(message)s", level=logging.INFO)

    if config.option.rts:
        if not os.path.isfile(DB_FILE_NAME):
            logger.info("No mapping database detected, starting initialization...")
            config.pluginmanager.register(InitPhasePlugin())
            return

        db_helper = DatabaseHelper()
        db_helper.init_conn()

        workdir_data = get_tests_and_data_current(db_helper)

        logger.info("WORKING DIRECTORY CHANGES")
        logger.info(
            "Found %s changed test files", workdir_data.changed_testfiles_amount
        )
        logger.info("Found %s changed src files", workdir_data.changed_srcfiles_amount)
        logger.info("Found %s tests to execute\n", len(workdir_data.test_set))

        if workdir_data.test_set:
            logger.info(
                "Running WORKING DIRECTORY test set and exiting without updating..."
            )
            config.pluginmanager.register(NormalPhasePlugin(workdir_data.test_set))
            return

        logger.info("No WORKING DIRECTORY tests to run, checking COMMITTED changes...")

        current_hash = get_current_head_hash()
        if db_helper.is_last_update_hash(current_hash):
            pytest.exit("Database is updated to the current commit state", 0)

        previous_hash = db_helper.get_last_update_hash()
        logger.info("Comparison: %s\n", " => ".join([current_hash, previous_hash]))

        committed_data = get_tests_and_data_committed(db_helper)

        logger.info("COMMITTED CHANGES")
        logger.info(
            "Found %s changed test files", committed_data.changed_testfiles_amount
        )
        logger.info(
            "Found %s changed src files", committed_data.changed_srcfiles_amount
        )
        logger.info(
            "Found %s newly added tests",
            committed_data.new_tests_amount,
        )
        logger.info("Found %s tests to execute\n", len(committed_data.test_set))

        if committed_data.warning_needed:
            logger.info(
                "WARNING: New lines were added to the following files but no new tests discovered:"
            )
            logger.info("\n".join(committed_data.files_to_warn))

        logger.info("=> Executing tests (if any) and updating database")
        db_helper.save_last_update_hash(current_hash)

        update_mapping_db(committed_data.update_data, db_helper)

        if committed_data.test_set:
            config.pluginmanager.register(UpdatePhasePlugin(committed_data.test_set))
            return

        pytest.exit("No tests to run", 0)