示例#1
0
class TestAnalyze(unittest.TestCase):
    _ccClient = None

    def setUp(self):

        # TEST_WORKSPACE is automatically set by test package __init__.py .
        self.test_workspace = os.environ['TEST_WORKSPACE']

        test_class = self.__class__.__name__
        print('Running ' + test_class + ' tests in ' + self.test_workspace)

        # Get the CodeChecker cmd if needed for the tests.
        self._codechecker_cmd = env.codechecker_cmd()
        self.report_dir = os.path.join(self.test_workspace, "reports")
        self.test_dir = os.path.join(os.path.dirname(__file__), 'test_files')
        # Change working dir to testfile dir so CodeChecker can be run easily.
        self.__old_pwd = os.getcwd()
        os.chdir(self.test_dir)

        self.missing_checker_regex = re.compile(
            r"No checker\(s\) with these names was found")

    def tearDown(self):
        """Restore environment after tests have ran."""
        os.chdir(self.__old_pwd)
        if os.path.isdir(self.report_dir):
            shutil.rmtree(self.report_dir)

    def __analyze_incremental(self, content_, build_json, reports_dir,
                              plist_count, failed_count):
        """
        Helper function to test analyze incremental mode. It's create a file
        with the given content. Run analyze on that file and checks the count
        of the plist end error files.
        """
        source_file = os.path.join(self.test_workspace, "simple.cpp")

        # Write content to the test file
        with open(source_file, 'w', encoding="utf-8",
                  errors="ignore") as source:
            source.write(content_)

        # Create analyze command.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", reports_dir
        ]

        # Run analyze
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        out, err = process.communicate()
        print(out)
        print(err)

        errcode = process.returncode
        # This function checks incremental analysis. There are some test cases
        # for failed analysis during incremental analysis, do the error code
        # can also be 3.
        self.assertIn(errcode, [0, 3])

        # Check the count of the plist files.
        plist_files = [
            os.path.join(reports_dir, filename)
            for filename in os.listdir(reports_dir)
            if filename.endswith('.plist')
        ]
        self.assertEqual(len(plist_files), plist_count)

        # Check the count of the error files.
        failed_dir = os.path.join(reports_dir, "failed")
        failed_file_count = 0
        if os.path.exists(failed_dir):
            failed_files = [
                os.path.join(failed_dir, filename)
                for filename in os.listdir(failed_dir)
                if filename.endswith('.zip')
            ]
            failed_file_count = len(failed_files)

            for f in failed_files:
                os.remove(f)
        self.assertEqual(failed_file_count, failed_count)

    @unittest.skipUnless(
        version.get("gcc"),
        "If gcc or g++ is a symlink to clang this test should be "
        "skipped. Option filtering is different for the two "
        "compilers. This test is gcc/g++ specific.")
    def test_compiler_info_files(self):
        '''
        Test that the compiler info files are generated
        '''
        # GIVEN
        build_json = os.path.join(self.test_workspace, "build_simple.json")
        reports_dir = self.report_dir
        source_file_cpp = os.path.join(self.test_workspace, "simple.cpp")
        source_file_c = os.path.join(self.test_workspace, "simple.c")

        # Create a compilation database.
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file_c,
            "file": source_file_c
        }, {
            "directory": self.test_workspace,
            "command": "clang++ -c " + source_file_cpp,
            "file": source_file_cpp
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        # Test file contents
        simple_file_content = "int main() { return 0; }"

        # Write content to the test file
        with open(source_file_cpp, 'w', encoding="utf-8",
                  errors="ignore") as source:
            source.write(simple_file_content)

        with open(source_file_c, 'w', encoding="utf-8",
                  errors="ignore") as source:
            source.write(simple_file_content)

        # Create analyze command.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", reports_dir
        ]

        # WHEN
        # Run analyze.
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        process.communicate()

        # THEN
        errcode = process.returncode
        self.assertEqual(errcode, 0)

        info_File = os.path.join(reports_dir, 'compiler_info.json')
        self.assertEqual(os.path.exists(info_File), True)
        self.assertNotEqual(os.stat(info_File).st_size, 0)

        # Test the validity of the json files.
        with open(info_File, 'r', encoding="utf-8", errors="ignore") as f:
            try:
                data = json.load(f)
                self.assertEqual(len(data), 1)
                # For clang we do not collect anything.
                self.assertTrue("g++" in data)
            except ValueError:
                self.fail("json.load should successfully parse the file %s" %
                          info_File)

    def test_compiler_info_file_is_loaded(self):
        '''
        Test that compiler info file is loaded if option is set.
        '''
        reports_dir = self.report_dir
        build_json = os.path.join(self.test_workspace, "build_simple.json")
        source_file = os.path.join(self.test_workspace, "simple.cpp")
        compiler_info_file = os.path.join(self.test_workspace,
                                          "compiler_info.json")

        # Create a compilation database.
        build_log = [{
            "directory": self.test_workspace,
            "command": "clang++ -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        # Test file contents
        simple_file_content = "int main() { return 0; }"

        # Write content to the test file
        with open(source_file, 'w', encoding="utf-8",
                  errors="ignore") as source:
            source.write(simple_file_content)

        with open(compiler_info_file, 'w', encoding="utf-8",
                  errors="ignore") as source:
            source.write('''{
  "clang++": {
    "c++": {
      "compiler_standard": "-std=FAKE_STD",
      "target": "FAKE_TARGET",
      "compiler_includes": [
        "-isystem /FAKE_INCLUDE_DIR"
      ]
    }
  }
}''')

        # Create analyze command.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json,
            "--compiler-info-file", compiler_info_file, "--analyzers",
            "clangsa", "--verbose", "debug", "-o", reports_dir
        ]
        # Run analyze.
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        out, _ = process.communicate()
        print(out)
        self.assertTrue("-std=FAKE_STD" in out)
        self.assertTrue("--target=FAKE_TARGET" in out)
        self.assertTrue("-isystem /FAKE_INCLUDE_DIR" in out)

    def test_capture_analysis_output(self):
        """
        Test if reports/success/<output_file>.[stdout,stderr].txt
        files are created
        """
        build_json = os.path.join(self.test_workspace, "build_success.json")
        success_dir = os.path.join(self.report_dir, "success")
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", self.report_dir, "--capture-analysis-output"
        ]

        source_file = os.path.join(self.test_dir, "success.c")
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        out, err = process.communicate()
        print(out)
        print(err)
        errcode = process.returncode
        self.assertEqual(errcode, 0)

        # We expect the sucess stderr file in the success directory.
        success_files = os.listdir(success_dir)
        print(success_files)
        self.assertEqual(len(success_files), 1)
        self.assertIn("success.c", success_files[0])
        os.remove(os.path.join(success_dir, success_files[0]))

    def test_failure(self):
        """
        Test if reports/failed/<failed_file>.zip file is created
        """
        build_json = os.path.join(self.test_workspace, "build.json")
        failed_dir = os.path.join(self.report_dir, "failed")
        source_file = os.path.join(self.test_dir, "failure.c")

        # Create a compilation database.
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        # Create and run analyze command.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "--verbose", "debug", "-o", self.report_dir
        ]

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        out, err = process.communicate()

        print(out)
        print(err)
        errcode = process.returncode
        self.assertEqual(errcode, 3)

        self.assertNotIn("UserWarning: Duplicate name", err)

        # We expect a failure archive to be in the failed directory.
        failed_files = os.listdir(failed_dir)
        self.assertEqual(len(failed_files), 1)

        fail_zip = os.path.join(failed_dir, failed_files[0])

        with zipfile.ZipFile(fail_zip, 'r') as archive:
            files = archive.namelist()

            self.assertIn("build-action", files)
            self.assertIn("analyzer-command", files)

            with archive.open("build-action", 'r') as archived_buildcmd:
                self.assertEqual(archived_buildcmd.read().decode("utf-8"),
                                 "gcc -c " + source_file)

            source_in_archive = os.path.join("sources-root",
                                             source_file.lstrip('/'))
            self.assertIn(source_in_archive, files)

            with archive.open(source_in_archive, 'r') as archived_code:
                with open(source_file, 'r', encoding="utf-8",
                          errors="ignore") as source_code:
                    self.assertEqual(archived_code.read().decode("utf-8"),
                                     source_code.read())

        shutil.rmtree(failed_dir)

    def test_reproducer(self):
        """
        Test if reports/reproducer/<reproducer_file>.zip file is created
        """
        build_json = os.path.join(self.test_workspace, "build.json")
        reproducer_dir = os.path.join(self.report_dir, "reproducer")
        source_file = os.path.join(self.test_dir, "failure.c")

        # Create a compilation database.
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        # Create and run analyze command.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "--verbose", "debug", "-o", self.report_dir,
            "--generate-reproducer", "-c"
        ]

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        out, err = process.communicate()

        print(out)
        print(err)
        errcode = process.returncode
        self.assertEqual(errcode, 3)
        self.assertNotIn('failed', os.listdir(self.report_dir))

        self.assertNotIn("UserWarning: Duplicate name", err)

        # We expect a failure archive to be in the failed directory.
        reproducer_files = os.listdir(reproducer_dir)
        self.assertEqual(len(reproducer_files), 1)

        fail_zip = os.path.join(reproducer_dir, reproducer_files[0])

        with zipfile.ZipFile(fail_zip, 'r') as archive:
            files = archive.namelist()

            self.assertIn("build-action", files)
            self.assertIn("analyzer-command", files)

            with archive.open("build-action", 'r') as archived_buildcmd:
                self.assertEqual(archived_buildcmd.read().decode("utf-8"),
                                 "gcc -c " + source_file)

            source_in_archive = os.path.join("sources-root",
                                             source_file.lstrip('/'))
            self.assertIn(source_in_archive, files)

            with archive.open(source_in_archive, 'r') as archived_code:
                with open(source_file, 'r', encoding="utf-8",
                          errors="ignore") as source_code:
                    self.assertEqual(archived_code.read().decode("utf-8"),
                                     source_code.read())

        shutil.rmtree(reproducer_dir)

    def test_robustness_for_dependencygen_failure(self):
        """
        Test if failure ZIP is created even if the dependency generator creates
        an invalid output.
        """
        build_json = os.path.join(self.test_workspace, "build.json")
        failed_dir = os.path.join(self.report_dir, "failed")
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "--verbose", "debug", "-o", self.report_dir
        ]

        source_file = os.path.join(self.test_dir, "failure.c")
        build_log = [{
            "directory": self.test_workspace,
            "command": "cc -c -std=c++11 " + source_file,
            "file": source_file
        }]

        # cc -std=c++11 writes error "-std=c++11 valid for C++ but not for C"
        # to its output when invoked as a dependency generator for this
        # build command.

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        process.communicate()

        errcode = process.returncode
        self.assertEqual(errcode, 3)

        # We expect a failure archive to be in the failed directory.
        failed_files = os.listdir(failed_dir)
        print(failed_files)
        self.assertEqual(len(failed_files), 1)
        self.assertIn("failure.c", failed_files[0])

        os.remove(os.path.join(failed_dir, failed_files[0]))

    def test_incremental_analyze(self):
        """
        Test incremental mode to analysis command which overwrites only those
        plist files that were update by the current build command.
        """
        build_json = os.path.join(self.test_workspace, "build_simple.json")
        reports_dir = os.path.join(self.test_workspace, "reports_incremental")
        source_file = os.path.join(self.test_workspace, "simple.cpp")

        # Create a compilation database.
        build_log = [{
            "directory": self.test_workspace,
            "command": "g++ -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        # Test file contents
        simple_file_content = "int main() { return 0; }"
        failed_file_content = "int main() { err; return 0; }"

        # Run analyze on the simple file.
        self.__analyze_incremental(simple_file_content, build_json,
                                   reports_dir, 1, 0)

        # Run analyze on the failed file.
        self.__analyze_incremental(failed_file_content, build_json,
                                   reports_dir, 0, 1)

        # Run analyze on the simple file again.
        self.__analyze_incremental(simple_file_content, build_json,
                                   reports_dir, 1, 0)

    def test_relative_include_paths(self):
        """
        Test if the build json contains relative paths.
        """
        build_json = os.path.join(self.test_workspace, "build_simple_rel.json")
        report_dir = os.path.join(self.test_workspace, "reports_relative")
        source_file = os.path.join(self.test_dir, "simple.c")
        failed_dir = os.path.join(report_dir, "failed")

        # Create a compilation database.
        build_log = [{
            "directory": self.test_dir,
            "command": "cc -c " + source_file + " -Iincludes",
            "file": source_file
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", report_dir
        ]
        # CodeChecker is executed in a different
        # dir than the containing folder of simple.c.
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_workspace,
                                   encoding="utf-8",
                                   errors="ignore")
        process.communicate()

        errcode = process.returncode
        self.assertEqual(errcode, 0)
        self.assertFalse(os.path.isdir(failed_dir))

    def test_tidyargs_saargs(self):
        """
        Test tidyargs and saargs config files
        """
        build_json = os.path.join(self.test_workspace, "build_extra_args.json")
        report_dir = os.path.join(self.test_workspace, "reports_extra_args")
        source_file = os.path.join(self.test_dir, "extra_args.cpp")
        tidyargs_file = os.path.join(self.test_dir, "tidyargs")
        saargs_file = os.path.join(self.test_dir, "saargs")

        build_log = [{
            "directory": self.test_dir,
            "command": "g++ -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "-o", report_dir,
            "--tidyargs", tidyargs_file, "--analyzer-config",
            'clang-tidy:HeaderFilterRegex=.*',
            'clang-tidy:Checks=modernize-use-bool-literals'
        ]

        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_workspace,
                                   encoding="utf-8",
                                   errors="ignore")
        process.communicate()

        process = subprocess.Popen(
            [self._codechecker_cmd, "parse", report_dir],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            cwd=self.test_workspace,
            encoding="utf-8",
            errors="ignore")
        out, _ = process.communicate()

        self.assertIn("division by zero", out)
        self.assertIn("modernize-avoid-bind", out)
        self.assertNotIn("performance-for-range-copy", out)

        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "-o", report_dir,
            "--saargs", saargs_file
        ]
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_workspace,
                                   encoding="utf-8",
                                   errors="ignore")
        process.communicate()

        process = subprocess.Popen(
            [self._codechecker_cmd, "parse", report_dir],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            cwd=self.test_workspace,
            encoding="utf-8",
            errors="ignore")
        out, _ = process.communicate()

        self.assertIn("Dereference of null pointer", out)

    def unique_json_helper(self, unique_json, is_a, is_b, is_s):
        with open(unique_json, encoding="utf-8", errors="ignore") as json_file:
            data = json.load(json_file)
            simple_a = False
            simple_b = False
            success = False
            for d in data:
                if "simple_a.o" in d["command"]:
                    simple_a = True
                if "simple_b.o" in d["command"]:
                    simple_b = True
                if "success.o" in d["command"]:
                    success = True
            self.assertEqual(simple_a, is_a)
            self.assertEqual(simple_b, is_b)
            self.assertEqual(success, is_s)

    def test_compile_uniqueing(self):
        """
        Test complilation uniqueing
        """
        build_json = os.path.join(self.test_workspace, "build_simple_rel.json")
        report_dir = os.path.join(self.test_workspace, "reports_relative")
        source_file = os.path.join(self.test_dir, "simple.c")
        source_file2 = os.path.join(self.test_dir, "success.c")
        failed_dir = os.path.join(report_dir, "failed")
        unique_json = os.path.join(report_dir, "unique_compile_commands.json")

        # Create a compilation database.
        build_log = [{
            "directory": self.test_dir,
            "command": "cc -c " + source_file + " -Iincludes -o simple_b.o",
            "file": source_file
        }, {
            "directory": self.test_dir,
            "command": "cc -c " + source_file + " -Iincludes -o simple_a.o",
            "file": source_file
        }, {
            "directory": self.test_dir,
            "command": "cc -c " + source_file2 + " -Iincludes -o success.o",
            "file": source_file2
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        # Testing alphabetic uniqueing mode.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", report_dir, "--compile-uniqueing", "alpha"
        ]

        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_workspace,
                                   encoding="utf-8",
                                   errors="ignore")
        process.communicate()

        errcode = process.returncode
        self.assertEqual(errcode, 0)
        self.assertFalse(os.path.isdir(failed_dir))

        self.unique_json_helper(unique_json, True, False, True)

        # Testing regex mode.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", report_dir, "--compile-uniqueing", ".*_b.*"
        ]
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_workspace,
                                   encoding="utf-8",
                                   errors="ignore")
        process.communicate()

        errcode = process.returncode
        self.assertEqual(errcode, 0)
        self.assertFalse(os.path.isdir(failed_dir))

        self.unique_json_helper(unique_json, False, True, True)

        # Testing regex mode.error handling
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", report_dir, "--compile-uniqueing", ".*simple.*"
        ]
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_workspace,
                                   encoding="utf-8",
                                   errors="ignore")
        process.communicate()

        errcode = process.returncode
        # Since .*simple.* matches 2 files, thus we get an error
        self.assertEqual(errcode, 1)

        # Testing strict mode
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", report_dir, "--compile-uniqueing", "strict",
            "--verbose", "debug"
        ]

        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_workspace,
                                   encoding="utf-8",
                                   errors="ignore")
        process.communicate()

        # In strict mode the analysis must fail
        # if there are more than one build
        # commands for a single source.
        errcode = process.returncode
        self.assertEqual(errcode, 1)
        self.assertFalse(os.path.isdir(failed_dir))

        # Testing None mode.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", report_dir, "--compile-uniqueing", "none"
        ]
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_workspace,
                                   encoding="utf-8",
                                   errors="ignore")
        process.communicate()

        errcode = process.returncode
        self.assertEqual(errcode, 0)
        self.assertFalse(os.path.isdir(failed_dir))
        self.unique_json_helper(unique_json, True, True, True)

    def test_invalid_enabled_checker_name(self):
        """Warn in case of an invalid enabled checker."""
        build_json = os.path.join(self.test_workspace, "build_success.json")
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", self.report_dir, "-e", "non-existing-checker-name"
        ]

        source_file = os.path.join(self.test_dir, "success.c")
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        out, _ = process.communicate()

        match = self.missing_checker_regex.search(out)
        self.assertIsNotNone(match)
        self.assertTrue("non-existing-checker-name" in out)

        errcode = process.returncode
        self.assertEqual(errcode, 0)

    def test_disable_all_warnings(self):
        """Test disabling warnings as checker groups."""
        build_json = os.path.join(self.test_workspace, "build.json")
        analyze_cmd = [
            self._codechecker_cmd, "check", "-l", build_json, "--analyzers",
            "clang-tidy", "-d", "clang-diagnostic", "-e",
            "clang-diagnostic-unused"
        ]

        source_file = os.path.join(self.test_dir, "compiler_warning.c")
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        out, _ = process.communicate()

        self.assertNotIn(
            "format specifies type 'int' but the argument has "
            "type 'char *' [clang-diagnostic-format]", out)
        self.assertIn("unused variable 'i' [clang-diagnostic-unused-variable]",
                      out)

    def test_invalid_disabled_checker_name(self):
        """Warn in case of an invalid disabled checker."""
        build_json = os.path.join(self.test_workspace, "build_success.json")
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", self.report_dir, "-d", "non-existing-checker-name"
        ]

        source_file = os.path.join(self.test_dir, "success.c")
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        out, _ = process.communicate()

        match = self.missing_checker_regex.search(out)
        self.assertIsNotNone(match)
        self.assertTrue("non-existing-checker-name" in out)

        errcode = process.returncode
        self.assertEqual(errcode, 0)

    def test_multiple_invalid_checker_names(self):
        """Warn in case of multiple invalid checker names."""
        build_json = os.path.join(self.test_workspace, "build_success.json")
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", self.report_dir, "-e",
            "non-existing-checker-name", "-e", "non-existing-checker", "-d",
            "missing.checker", "-d", "other.missing.checker"
        ]

        source_file = os.path.join(self.test_dir, "success.c")
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        out, _ = process.communicate()

        match = self.missing_checker_regex.search(out)
        self.assertIsNotNone(match)
        self.assertTrue("non-existing-checker-name" in out)
        self.assertTrue("non-existing-checker" in out)
        self.assertTrue("missing.checker" in out)
        self.assertTrue("other.missing.checker" in out)

        errcode = process.returncode

        self.assertEqual(errcode, 0)

    def test_makefile_generation(self):
        """ Test makefile generation. """
        build_json = os.path.join(self.test_workspace, "build_extra_args.json")
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "-o",
            self.report_dir, '--makefile'
        ]

        source_file = os.path.join(self.test_dir, "extra_args.cpp")
        build_log = [{
            "directory": self.test_workspace,
            "command": "g++ -DTIDYARGS -c " + source_file,
            "file": source_file
        }, {
            "directory": self.test_workspace,
            "command": "g++ -DSAARGS -DTIDYARGS -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        process.communicate()

        errcode = process.returncode
        self.assertEqual(errcode, 0)

        # Check the existence of the Makefile.
        makefile = os.path.join(self.report_dir, 'Makefile')
        self.assertTrue(os.path.exists(makefile))

        # Run the generated Makefile and check the return code of it.
        process = subprocess.Popen(["make"],
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.report_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        process.communicate()

        errcode = process.returncode
        self.assertEqual(errcode, 0)

        plist_files = glob.glob(os.path.join(self.report_dir, '*.plist'))
        self.assertEqual(len(plist_files), 4)

    def test_analyzer_and_checker_config(self):
        """Test analyzer configuration through command line flags."""
        build_json = os.path.join(self.test_workspace, "build_success.json")
        source_file = os.path.join(self.test_dir, "checker_config.cpp")
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            json.dump(build_log, outfile)

        analyze_cmd = [
            self._codechecker_cmd, "check", "-l", build_json, "--analyzers",
            "clang-tidy", "-o", self.report_dir, "--analyzer-config",
            "clang-tidy:Checks=hicpp-use-nullptr"
        ]

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        out, _ = process.communicate()

        print(out)

        # It's printed as a found report and in the checker statistics.
        self.assertEqual(out.count('hicpp-use-nullptr'), 2)

        analyze_cmd = [
            self._codechecker_cmd, "check", "-l", build_json, "--analyzers",
            "clang-tidy", "-o", self.report_dir, "--analyzer-config",
            "clang-tidy:Checks=hicpp-use-nullptr", "--checker-config",
            "clang-tidy:hicpp-use-nullptr.NullMacros=MY_NULL"
        ]

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        out, _ = process.communicate()

        # It's printed as the member of enabled checkers at the beginning
        # of the output, a found report and in the checker statistics.
        self.assertEqual(out.count('hicpp-use-nullptr'), 3)

        analyze_cmd = [
            self._codechecker_cmd, "check", "-l", build_json, "--analyzers",
            "clangsa", "-o", self.report_dir, "--checker-config",
            "clangsa:optin.cplusplus.UninitializedObject:Pedantic"
            "=true"
        ]

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        out, _ = process.communicate()
        print(out)
        # It's printed as the member of enabled checkers at the beginning
        # of the output, a found report and in the checker statistics.
        self.assertEqual(out.count('UninitializedObject'), 3)

        analyze_cmd = [
            self._codechecker_cmd, "check", "-l", build_json, "--analyzers",
            "clangsa", "-o", self.report_dir, "--checker-config",
            "clangsa:optin.cplusplus.UninitializedObject:Pedantic"
            "=true", "--analyzer-config", "clangsa:max-nodes=1"
        ]

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")
        out, _ = process.communicate()

        print(out)
        # It is printed as the member of enabled checkers, but it gives no
        # report.
        self.assertEqual(out.count('UninitializedObject'), 1)

    def test_invalid_compilation_database(self):
        """ Warn in case of an invalid enabled checker. """
        build_json = os.path.join(self.test_workspace, "build_corrupted.json")
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "-o", self.report_dir
        ]

        with open(build_json, 'w', encoding="utf-8",
                  errors="ignore") as outfile:
            outfile.write("Corrupted JSON file!")

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir,
                                   encoding="utf-8",
                                   errors="ignore")

        process.communicate()

        self.assertEqual(process.returncode, 1)
示例#2
0
class TestAnalyze(unittest.TestCase):
    _ccClient = None

    def setUp(self):

        # TEST_WORKSPACE is automatically set by test package __init__.py .
        self.test_workspace = os.environ['TEST_WORKSPACE']

        test_class = self.__class__.__name__
        print('Running ' + test_class + ' tests in ' + self.test_workspace)

        # Get the CodeChecker cmd if needed for the tests.
        self._codechecker_cmd = env.codechecker_cmd()
        self.report_dir = os.path.join(self.test_workspace, "reports")
        self.test_dir = os.path.join(os.path.dirname(__file__), 'test_files')
        # Change working dir to testfile dir so CodeChecker can be run easily.
        self.__old_pwd = os.getcwd()
        os.chdir(self.test_dir)

        self.missing_checker_regex = re.compile(
            r"No checker\(s\) with these names was found")

    def tearDown(self):
        """Restore environment after tests have ran."""
        os.chdir(self.__old_pwd)
        if os.path.isdir(self.report_dir):
            shutil.rmtree(self.report_dir)

    def __analyze_incremental(self, content_, build_json, reports_dir,
                              plist_count, failed_count):
        """
        Helper function to test analyze incremental mode. It's create a file
        with the given content. Run analyze on that file and checks the count
        of the plist end error files.
        """
        source_file = os.path.join(self.test_workspace, "simple.cpp")

        # Write content to the test file
        with open(source_file, 'w') as source:
            source.write(content_)

        # Create analyze command.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", reports_dir
        ]

        # Run analyze
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir)
        out, err = process.communicate()
        print(out)
        print(err)

        errcode = process.returncode
        self.assertEquals(errcode, 0)

        # Check the count of the plist files.
        plist_files = [
            os.path.join(reports_dir, filename)
            for filename in os.listdir(reports_dir)
            if filename.endswith('.plist')
        ]
        self.assertEquals(len(plist_files), plist_count)

        # Check the count of the error files.
        failed_dir = os.path.join(reports_dir, "failed")
        failed_file_count = 0
        if os.path.exists(failed_dir):
            failed_files = [
                os.path.join(failed_dir, filename)
                for filename in os.listdir(failed_dir)
                if filename.endswith('.zip')
            ]
            failed_file_count = len(failed_files)

            for f in failed_files:
                os.remove(f)
        self.assertEquals(failed_file_count, failed_count)

    @unittest.skipIf(
        version.get("gcc") is not None,
        "If gcc or g++ is a symlink to clang this test should be "
        "skipped. Option filtering is different for the two "
        "compilers. This test is gcc/g++ specific.")
    def test_compiler_info_files(self):
        '''
        Test that the compiler info files are generated
        '''
        # GIVEN
        build_json = os.path.join(self.test_workspace, "build_simple.json")
        reports_dir = self.report_dir
        source_file_cpp = os.path.join(self.test_workspace, "simple.cpp")
        source_file_c = os.path.join(self.test_workspace, "simple.c")

        # Create a compilation database.
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file_c,
            "file": source_file_c
        }, {
            "directory": self.test_workspace,
            "command": "clang++ -c " + source_file_cpp,
            "file": source_file_cpp
        }]

        with open(build_json, 'w') as outfile:
            json.dump(build_log, outfile)

        # Test file contents
        simple_file_content = "int main() { return 0; }"

        # Write content to the test file
        with open(source_file_cpp, 'w') as source:
            source.write(simple_file_content)

        with open(source_file_c, 'w') as source:
            source.write(simple_file_content)

        # Create analyze command.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", reports_dir
        ]

        # WHEN
        # Run analyze.
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir)
        process.communicate()

        # THEN
        errcode = process.returncode
        self.assertEquals(errcode, 0)

        info_File = os.path.join(reports_dir, 'compiler_info.json')
        self.assertEquals(os.path.exists(info_File), True)
        self.assertNotEqual(os.stat(info_File).st_size, 0)

        # Test the validity of the json files.
        with open(info_File, 'r') as f:
            try:
                data = json.load(f)
                self.assertEquals(len(data), 1)
                # For clang we do not collect anything.
                self.assertTrue("g++" in data)
            except ValueError:
                self.fail("json.load should successfully parse the file %s" %
                          info_File)

    def test_compiler_info_file_is_loaded(self):
        '''
        Test that compiler info file is loaded if option is set.
        '''
        reports_dir = self.report_dir
        build_json = os.path.join(self.test_workspace, "build_simple.json")
        source_file = os.path.join(self.test_workspace, "simple.cpp")
        compiler_info_file = os.path.join(self.test_workspace,
                                          "compiler_info.json")

        # Create a compilation database.
        build_log = [{
            "directory": self.test_workspace,
            "command": "clang++ -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w') as outfile:
            json.dump(build_log, outfile)

        # Test file contents
        simple_file_content = "int main() { return 0; }"

        # Write content to the test file
        with open(source_file, 'w') as source:
            source.write(simple_file_content)

        with open(compiler_info_file, 'w') as source:
            source.write('''{
  "clang++": {
    "c++": {
      "compiler_standard": "-std=FAKE_STD",
      "target": "FAKE_TARGET",
      "compiler_includes": [
        "-isystem /FAKE_INCLUDE_DIR"
      ]
    }
  }
}''')

        # Create analyze command.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json,
            "--compiler-info-file", compiler_info_file, "--analyzers",
            "clangsa", "--verbose", "debug", "-o", reports_dir
        ]
        # Run analyze.
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir)
        out, _ = process.communicate()
        print(out)
        self.assertTrue("-std=FAKE_STD" in out)
        self.assertTrue("--target=FAKE_TARGET" in out)
        self.assertTrue("-isystem /FAKE_INCLUDE_DIR" in out)

    def test_capture_analysis_output(self):
        """
        Test if reports/success/<output_file>.[stdout,stderr].txt
        files are created
        """
        build_json = os.path.join(self.test_workspace, "build_success.json")
        success_dir = os.path.join(self.report_dir, "success")
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", self.report_dir, "--capture-analysis-output"
        ]

        source_file = os.path.join(self.test_dir, "success.c")
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w') as outfile:
            json.dump(build_log, outfile)

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir)
        out, err = process.communicate()
        print(out)
        print(err)
        errcode = process.returncode
        self.assertEquals(errcode, 0)

        # We expect the sucess stderr file in the success directory.
        success_files = os.listdir(success_dir)
        print(success_files)
        self.assertEquals(len(success_files), 1)
        self.assertIn("success.c", success_files[0])
        os.remove(os.path.join(success_dir, success_files[0]))

    def test_failure(self):
        """
        Test if reports/failed/<failed_file>.zip file is created
        """
        build_json = os.path.join(self.test_workspace, "build.json")
        failed_dir = os.path.join(self.report_dir, "failed")
        source_file = os.path.join(self.test_dir, "failure.c")

        # Create a compilation database.
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w') as outfile:
            json.dump(build_log, outfile)

        # Create and run analyze command.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "--verbose", "debug", "-o", self.report_dir
        ]

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir)
        out, err = process.communicate()

        print(out)
        print(err)
        errcode = process.returncode
        self.assertEquals(errcode, 0)

        # We expect a failure archive to be in the failed directory.
        failed_files = os.listdir(failed_dir)
        self.assertEquals(len(failed_files), 1)

        fail_zip = os.path.join(failed_dir, failed_files[0])

        with zipfile.ZipFile(fail_zip, 'r') as archive:
            files = archive.namelist()

            self.assertIn("build-action", files)
            self.assertIn("analyzer-command", files)

            with archive.open("build-action", 'r') as archived_buildcmd:
                self.assertEqual(archived_buildcmd.read(),
                                 "gcc -c " + source_file)

            source_in_archive = os.path.join("sources-root",
                                             source_file.lstrip('/'))
            self.assertIn(source_in_archive, files)

            with archive.open(source_in_archive, 'r') as archived_code:
                with open(source_file, 'r') as source_code:
                    self.assertEqual(archived_code.read(), source_code.read())

        os.remove(os.path.join(failed_dir, failed_files[0]))

    def test_robustness_for_dependencygen_failure(self):
        """
        Test if failure ZIP is created even if the dependency generator creates
        an invalid output.
        """
        build_json = os.path.join(self.test_workspace, "build.json")
        failed_dir = os.path.join(self.report_dir, "failed")
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "--verbose", "debug", "-o", self.report_dir
        ]

        source_file = os.path.join(self.test_dir, "failure.c")
        build_log = [{
            "directory": self.test_workspace,
            "command": "cc -c -std=c++11 " + source_file,
            "file": source_file
        }]

        # cc -std=c++11 writes error "-std=c++11 valid for C++ but not for C"
        # to its output when invoked as a dependency generator for this
        # build command.

        with open(build_json, 'w') as outfile:
            json.dump(build_log, outfile)

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir)
        process.communicate()

        errcode = process.returncode
        self.assertEquals(errcode, 0)

        # We expect a failure archive to be in the failed directory.
        failed_files = os.listdir(failed_dir)
        print(failed_files)
        self.assertEquals(len(failed_files), 1)
        self.assertIn("failure.c", failed_files[0])

        os.remove(os.path.join(failed_dir, failed_files[0]))

    def test_incremental_analyze(self):
        """
        Test incremental mode to analysis command which overwrites only those
        plist files that were update by the current build command.
        """
        build_json = os.path.join(self.test_workspace, "build_simple.json")
        reports_dir = os.path.join(self.test_workspace, "reports_incremental")
        source_file = os.path.join(self.test_workspace, "simple.cpp")

        # Create a compilation database.
        build_log = [{
            "directory": self.test_workspace,
            "command": "g++ -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w') as outfile:
            json.dump(build_log, outfile)

        # Test file contents
        simple_file_content = "int main() { return 0; }"
        failed_file_content = "int main() { err; return 0; }"

        # Run analyze on the simple file.
        self.__analyze_incremental(simple_file_content, build_json,
                                   reports_dir, 1, 0)

        # Run analyze on the failed file.
        self.__analyze_incremental(failed_file_content, build_json,
                                   reports_dir, 0, 1)

        # Run analyze on the simple file again.
        self.__analyze_incremental(simple_file_content, build_json,
                                   reports_dir, 1, 0)

    def test_relative_include_paths(self):
        """
        Test if the build json contains relative paths.
        """
        build_json = os.path.join(self.test_workspace, "build_simple_rel.json")
        report_dir = os.path.join(self.test_workspace, "reports_relative")
        source_file = os.path.join(self.test_dir, "simple.c")
        failed_dir = os.path.join(report_dir, "failed")

        # Create a compilation database.
        build_log = [{
            "directory": self.test_dir,
            "command": "cc -c " + source_file + " -Iincludes",
            "file": source_file
        }]

        with open(build_json, 'w') as outfile:
            json.dump(build_log, outfile)

        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", report_dir
        ]
        # CodeChecker is executed in a different
        # dir than the containing folder of simple.c.
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_workspace)
        process.communicate()

        errcode = process.returncode
        self.assertEquals(errcode, 0)
        self.assertFalse(os.path.isdir(failed_dir))

    def unique_json_helper(self, unique_json, is_a, is_b, is_s):
        with open(unique_json) as json_file:
            data = json.load(json_file)
            simple_a = False
            simple_b = False
            success = False
            for d in data:
                if "simple_a.o" in d["command"]:
                    simple_a = True
                if "simple_b.o" in d["command"]:
                    simple_b = True
                if "success.o" in d["command"]:
                    success = True
            self.assertEqual(simple_a, is_a)
            self.assertEqual(simple_b, is_b)
            self.assertEqual(success, is_s)

    def test_compile_uniqueing(self):
        """
        Test complilation uniqueing
        """
        build_json = os.path.join(self.test_workspace, "build_simple_rel.json")
        report_dir = os.path.join(self.test_workspace, "reports_relative")
        source_file = os.path.join(self.test_dir, "simple.c")
        source_file2 = os.path.join(self.test_dir, "success.c")
        failed_dir = os.path.join(report_dir, "failed")
        unique_json = os.path.join(report_dir, "unique_compile_commands.json")

        # Create a compilation database.
        build_log = [{
            "directory": self.test_dir,
            "command": "cc -c " + source_file + " -Iincludes -o simple_b.o",
            "file": source_file
        }, {
            "directory": self.test_dir,
            "command": "cc -c " + source_file + " -Iincludes -o simple_a.o",
            "file": source_file
        }, {
            "directory": self.test_dir,
            "command": "cc -c " + source_file2 + " -Iincludes -o success.o",
            "file": source_file2
        }]

        with open(build_json, 'w') as outfile:
            json.dump(build_log, outfile)

        # Testing alphabetic uniqueing mode.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", report_dir, "--compile-uniqueing", "alpha"
        ]

        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_workspace)
        process.communicate()

        errcode = process.returncode
        self.assertEquals(errcode, 0)
        self.assertFalse(os.path.isdir(failed_dir))

        self.unique_json_helper(unique_json, True, False, True)

        # Testing regex mode.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", report_dir, "--compile-uniqueing", ".*_b.*"
        ]
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_workspace)
        process.communicate()

        errcode = process.returncode
        self.assertEquals(errcode, 0)
        self.assertFalse(os.path.isdir(failed_dir))

        self.unique_json_helper(unique_json, False, True, True)

        # Testing regex mode.error handling
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", report_dir, "--compile-uniqueing", ".*simple.*"
        ]
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_workspace)
        process.communicate()

        errcode = process.returncode
        # Since .*simple.* matches 2 files, thus we get an error
        self.assertEquals(errcode, 1)

        # Testing strict mode
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", report_dir, "--compile-uniqueing", "strict",
            "--verbose", "debug"
        ]

        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_workspace)
        process.communicate()

        # In strict mode the analysis must fail
        # if there are more than one build
        # commands for a single source.
        errcode = process.returncode
        self.assertEquals(errcode, 1)
        self.assertFalse(os.path.isdir(failed_dir))

        # Testing None mode.
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", report_dir, "--compile-uniqueing", "none"
        ]
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_workspace)
        process.communicate()

        errcode = process.returncode
        self.assertEquals(errcode, 0)
        self.assertFalse(os.path.isdir(failed_dir))
        self.unique_json_helper(unique_json, True, True, True)

    def test_invalid_enabled_checker_name(self):
        """Warn in case of an invalid enabled checker."""
        build_json = os.path.join(self.test_workspace, "build_success.json")
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", self.report_dir, "-e", "non-existing-checker-name"
        ]

        source_file = os.path.join(self.test_dir, "success.c")
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w') as outfile:
            json.dump(build_log, outfile)

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir)
        out, _ = process.communicate()

        match = self.missing_checker_regex.search(out)
        self.assertIsNotNone(match)
        self.assertTrue("non-existing-checker-name" in out)

        errcode = process.returncode
        self.assertEquals(errcode, 0)

    def test_invalid_disabled_checker_name(self):
        """Warn in case of an invalid disabled checker."""
        build_json = os.path.join(self.test_workspace, "build_success.json")
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", self.report_dir, "-d", "non-existing-checker-name"
        ]

        source_file = os.path.join(self.test_dir, "success.c")
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w') as outfile:
            json.dump(build_log, outfile)

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir)
        out, _ = process.communicate()

        match = self.missing_checker_regex.search(out)
        self.assertIsNotNone(match)
        self.assertTrue("non-existing-checker-name" in out)

        errcode = process.returncode
        self.assertEquals(errcode, 0)

    def test_multiple_invalid_checker_names(self):
        """Warn in case of multiple invalid checker names."""
        build_json = os.path.join(self.test_workspace, "build_success.json")
        analyze_cmd = [
            self._codechecker_cmd, "analyze", build_json, "--analyzers",
            "clangsa", "-o", self.report_dir, "-e",
            "non-existing-checker-name", "-e", "non-existing-checker", "-d",
            "missing.checker", "-d", "other.missing.checker"
        ]

        source_file = os.path.join(self.test_dir, "success.c")
        build_log = [{
            "directory": self.test_workspace,
            "command": "gcc -c " + source_file,
            "file": source_file
        }]

        with open(build_json, 'w') as outfile:
            json.dump(build_log, outfile)

        print(analyze_cmd)
        process = subprocess.Popen(analyze_cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=self.test_dir)
        out, _ = process.communicate()

        match = self.missing_checker_regex.search(out)
        self.assertIsNotNone(match)
        self.assertTrue("non-existing-checker-name" in out)
        self.assertTrue("non-existing-checker" in out)
        self.assertTrue("missing.checker" in out)
        self.assertTrue("other.missing.checker" in out)

        errcode = process.returncode
        self.assertEquals(errcode, 0)