Пример #1
0
def test_discovery_plugin_file_cmd_exists():
    """Test when file command exists."""
    dp = DiscoveryPlugin()
    if not dp.file_command_exists():
        pytest.skip(
            "File command does not exist. Skipping test that requires it.")
    assert dp.file_command_exists()
Пример #2
0
def test_discovery_plugin_find_files():
    """Test calling find files."""
    dp = DiscoveryPlugin()
    if not dp.file_command_exists():
        pytest.skip(
            "File command does not exist. Skipping test that requires it.")
    package = Package("valid_package",
                      os.path.join(os.path.dirname(__file__), "valid_package"))
    expected = [
        "CMakeLists.txt",
        "package.xml",
        "test.cpp",
        "test.sh",
    ]
    expected_fullpath = [
        os.path.join(package.path, filename) for filename in expected
    ]
    expected_file_cmd_out = [
        fullpath + ": empty\n" for fullpath in expected_fullpath
    ]
    expected_dict = {}
    for i, filename in enumerate(expected):
        expected_dict[expected_fullpath[i]] = {
            "name": filename.lower(),
            "path": expected_fullpath[i],
            "file_cmd_out": expected_file_cmd_out[i].lower(),
        }

    dp.find_files(package)

    assert package._walked  # pylint: disable=protected-access
    assert package.files == expected_dict
Пример #3
0
def test_discovery_plugin_get_file_cmd_output():
    """Test get_file_cmd_output."""
    dp = DiscoveryPlugin()
    package = Package(
        "valid_package", os.path.join(os.path.dirname(__file__), "valid_package")
    )
    filepath = os.path.join(package.path, "CMakeLists.txt")
    assert filepath.lower() + ": empty\n" == dp.get_file_cmd_output(filepath)
Пример #4
0
def test_discovery_plugin_get_file_cmd_output_no_file_cmd():
    """Test get_file_cmd_output when file command does not exist."""
    with modified_environ(PATH=""):
        dp = DiscoveryPlugin()
        package = Package(
            "valid_package",
            os.path.join(os.path.dirname(__file__), "valid_package"))
        filepath = os.path.join(package.path, "CMakeLists.txt")
        assert "" == dp.get_file_cmd_output(filepath)
Пример #5
0
def test_discovery_plugin_get_file_cmd_output():
    """Test get_file_cmd_output."""
    dp = DiscoveryPlugin()
    if not dp.file_command_exists():
        pytest.skip(
            "File command does not exist. Skipping test that requires it.")
    package = Package("valid_package",
                      os.path.join(os.path.dirname(__file__), "valid_package"))
    filepath = os.path.join(package.path, "CMakeLists.txt")
    assert filepath.lower() + ": empty\n" == dp.get_file_cmd_output(filepath)
Пример #6
0
def test_discovery_plugin_find_files_multiple():
    """Test that find_files will only walk the path once."""
    dp = DiscoveryPlugin()
    package = Package("valid_package",
                      os.path.join(os.path.dirname(__file__), "valid_package"))
    package._walked = True  # pylint: disable=protected-access
    expected_dict = {}

    dp.find_files(package)

    assert package._walked  # pylint: disable=protected-access
    assert package.files == expected_dict
Пример #7
0
def test_discovery_plugin_get_file_cmd_output_calledprocess_error(
    mock_subprocess_check_output, ):
    """
    Test what happens when a CalledProcessError is raised.

    Expected result: returned empty string.
    """
    mock_subprocess_check_output.side_effect = subprocess.CalledProcessError(
        1, "", output="mocked error")
    dp = DiscoveryPlugin()
    package = Package("valid_package",
                      os.path.join(os.path.dirname(__file__), "valid_package"))
    filepath = os.path.join(package.path, "CMakeLists.txt")
    assert "" == dp.get_file_cmd_output(filepath)
Пример #8
0
def test_scan_package_with_issues(init_statick_ws):
    """Test running Statick via the scan_package function used in multiprocessing."""
    if not DiscoveryPlugin.file_command_exists():
        pytest.skip("File command does not exist. Skipping test that requires it.")
    statick = init_statick_ws[0]
    args = init_statick_ws[1]
    sys.argv = [
        "--output-directory",
        os.path.dirname(__file__),
        "--path",
        os.path.join(os.path.dirname(__file__), "test_package"),
        "--profile",
        os.path.join(os.path.dirname(__file__), "rsc", "profile-custom.yaml"),
        "--config",
        os.path.join(os.path.dirname(__file__), "rsc", "config.yaml"),
        "--exceptions",
        os.path.join(os.path.dirname(__file__), "rsc", "exceptions.yaml"),
    ]

    parsed_args = args.get_args(sys.argv)
    path = parsed_args.path
    statick.get_config(parsed_args)
    statick.get_exceptions(parsed_args)
    package = Package("test_package", path)

    issues = statick.scan_package(parsed_args, 1, package, 1)

    assert len(issues["pylint"]) == 1

    try:
        shutil.rmtree(os.path.join(os.path.dirname(__file__), "test_package-custom"))
    except OSError as ex:
        print(f"Error: {ex}")
Пример #9
0
def test_run_workspace_with_issues(init_statick_ws):
    """
    Test that existing issues are found in a workspace.

    Expected results: issues is empty and success is True
    """
    if not DiscoveryPlugin.file_command_exists():
        pytest.skip("File command does not exist. Skipping test that requires it.")
    statick = init_statick_ws[0]
    args = init_statick_ws[1]
    sys.argv = init_statick_ws[2]
    sys.argv.extend(
        [
            "--profile",
            os.path.join(os.path.dirname(__file__), "rsc", "profile-custom.yaml"),
            "--config",
            os.path.join(os.path.dirname(__file__), "rsc", "config.yaml"),
            "--exceptions",
            os.path.join(os.path.dirname(__file__), "rsc", "exceptions.yaml"),
        ]
    )

    parsed_args = args.get_args(sys.argv)
    statick.get_config(parsed_args)
    statick.get_exceptions(parsed_args)
    print(f"parsed_args: {parsed_args}")

    issues, success = statick.run_workspace(parsed_args)

    # We expect two docstring errors.
    assert len(issues["pylint"]) == 2
    assert not success
Пример #10
0
    def scan(self, package, level, exceptions=None):
        """Scan package looking for C files."""
        c_files = []
        c_extensions = ('.c', '.cc', '.cpp', '.cxx', '.h', '.hxx', '.hpp')
        file_cmd_exists = True
        if not DiscoveryPlugin.file_command_exists():
            file_cmd_exists = False

        for root, _, files in os.walk(package.path):
            for f in files:
                if f.lower().endswith(c_extensions):
                    full_path = os.path.join(root, f)
                    c_files.append(os.path.abspath(full_path))
                elif file_cmd_exists:
                    full_path = os.path.join(root, f)
                    output = subprocess.check_output(["file", full_path],
                                                     universal_newlines=True)
                    if ("c source" in output.lower() or
                            "c++ source" in output.lower()) and not \
                            f.endswith(".cfg"):
                        c_files.append(os.path.abspath(full_path))

        c_files = list(OrderedDict.fromkeys(c_files))

        print("  {} C/C++ files found.".format(len(c_files)))
        if exceptions:
            original_file_count = len(c_files)
            c_files = exceptions.filter_file_exceptions_early(package, c_files)
            if original_file_count > len(c_files):
                print("  After filtering, {} C/C++ files will be scanned.".
                      format(len(c_files)))

        package["c_src"] = c_files
Пример #11
0
    def scan(self, package, level, exceptions=None):
        """Scan package looking for Perl files."""
        perl_files = []

        file_cmd_exists = True
        if not DiscoveryPlugin.file_command_exists():
            file_cmd_exists = False

        for root, _, files in os.walk(package.path):
            for f in fnmatch.filter(files, "*.pl"):
                full_path = os.path.join(root, f)
                perl_files.append(os.path.abspath(full_path))

            if file_cmd_exists:
                for f in files:
                    full_path = os.path.join(root, f)
                    output = subprocess.check_output(["file", full_path],
                                                     universal_newlines=True)
                    if "perl script" in output.lower():
                        perl_files.append(os.path.abspath(full_path))

        perl_files = list(OrderedDict.fromkeys(perl_files))

        print("  {} Perl files found.".format(len(perl_files)))
        if exceptions:
            original_file_count = len(perl_files)
            perl_files = exceptions.filter_file_exceptions_early(package, perl_files)
            if original_file_count > len(perl_files):
                print("  After filtering, {} perl files will be scanned.".
                      format(len(perl_files)))

        package["perl_src"] = perl_files
Пример #12
0
    def scan(self,
             package: Package,
             level: str,
             exceptions: Exceptions = None) -> None:
        """Scan package looking for TeX files."""
        tex_files = []  # type: List[str]
        globs = ["*.tex", "*.bib"]  # type: List[str]

        file_cmd_exists = True  # type: bool
        if not DiscoveryPlugin.file_command_exists():
            file_cmd_exists = False

        root = ""  # type: str
        for root, _, files in os.walk(package.path):
            for glob in globs:
                for f in fnmatch.filter(files, glob):
                    full_path = os.path.join(root, f)
                    tex_files.append(os.path.abspath(full_path))

            if file_cmd_exists:
                for f in files:
                    full_path = os.path.join(root, f)
                    output = subprocess.check_output(["file", full_path],
                                                     universal_newlines=True)
                    if f.endswith(".sty") or f.endswith(".log") or f.endswith(
                            ".cls"):
                        continue
                    # pylint: disable=unsupported-membership-test
                    if ("LaTeX document" in output
                            or "BibTeX text file" in output
                            or "LaTeX 2e document" in output):
                        # pylint: enable=unsupported-membership-test
                        tex_files.append(os.path.abspath(full_path))

        tex_files = list(OrderedDict.fromkeys(tex_files))

        print("  {} TeX files found.".format(len(tex_files)))
        if exceptions:
            original_file_count = len(tex_files)  # type: int
            tex_files = exceptions.filter_file_exceptions_early(
                package, tex_files)
            if original_file_count > len(tex_files):
                print(
                    "  After filtering, {} TeX files will be scanned.".format(
                        len(tex_files)))

        package["tex"] = tex_files
Пример #13
0
    def scan(self, package: Package, level: str, exceptions: Exceptions = None) -> None:
        """Scan package looking for python files."""
        python_files = []  # type: List[str]

        file_cmd_exists = True  # type: bool
        if not DiscoveryPlugin.file_command_exists():
            file_cmd_exists = False

        for root, _, files in os.walk(package.path):
            for f in fnmatch.filter(files, "*.py"):
                full_path = os.path.join(root, f)
                python_files.append(os.path.abspath(full_path))

            if file_cmd_exists:
                for f in files:
                    full_path = os.path.join(root, f)
                    output = subprocess.check_output(
                        ["file", full_path], universal_newlines=True
                    )  # type: str
                    # pylint: disable=unsupported-membership-test
                    if (
                        "python script" in output or "Python script" in output
                    ) and not f.endswith(".cfg"):
                        # pylint: enable=unsupported-membership-test
                        python_files.append(os.path.abspath(full_path))

        python_files = list(OrderedDict.fromkeys(python_files))

        print("  {} python files found.".format(len(python_files)))
        if exceptions:
            original_file_count = len(python_files)
            python_files = exceptions.filter_file_exceptions_early(
                package, python_files
            )
            if original_file_count > len(python_files):
                print(
                    "  After filtering, {} python files will be scanned.".format(
                        len(python_files)
                    )
                )

        package["python_src"] = python_files
Пример #14
0
    def scan(self, package, level, exceptions=None):
        """Scan package looking for HTML files."""
        src_files = []
        globs = ["*.html"]

        file_cmd_exists = True
        if not DiscoveryPlugin.file_command_exists():
            file_cmd_exists = False

        root = ''
        files = []
        for root, _, files in os.walk(package.path):
            for glob in globs:
                for f in fnmatch.filter(files, glob):
                    full_path = os.path.join(root, f)
                    src_files.append(os.path.abspath(full_path))

            if file_cmd_exists:
                for f in files:
                    full_path = os.path.join(root, f)
                    output = subprocess.check_output(["file", full_path],
                                                     universal_newlines=True)
                    # pylint: disable=unsupported-membership-test
                    if "HTML document" in output:
                        # pylint: enable=unsupported-membership-test
                        src_files.append(os.path.abspath(full_path))

        src_files = list(OrderedDict.fromkeys(src_files))

        print("  {} HTML source files found.".format(len(src_files)))
        if exceptions:
            original_file_count = len(src_files)
            src_files = exceptions.filter_file_exceptions_early(package,
                                                                src_files)
            if original_file_count > len(src_files):
                print("  After filtering, {} HTML files will be scanned.".
                      format(len(src_files)))

        package["html_src"] = src_files
Пример #15
0
    def run(
        self, path: str, args: argparse.Namespace
    ) -> Tuple[Optional[Dict[str, List[Issue]]], bool]:
        """Run scan tools against targets on path."""
        success = True

        path = os.path.abspath(path)
        if not os.path.exists(path):
            logging.error("No package found at %s!", path)
            return None, False

        package = Package(os.path.basename(path), path)
        level: Optional[str] = self.get_level(path, args)
        logging.info("level: %s", level)
        if level is None:
            logging.error("Level is not valid.")
            return None, False

        if not self.config or not self.config.has_level(level):
            logging.error("Can't find specified level %s in config!", level)
            return None, False

        orig_path = os.getcwd()
        if args.output_directory:
            if not os.path.isdir(args.output_directory):
                try:
                    os.mkdir(args.output_directory)
                except OSError as ex:
                    logging.error(
                        "Unable to create output directory at %s: %s",
                        args.output_directory,
                        ex,
                    )
                    return None, False

            output_dir = os.path.join(args.output_directory,
                                      package.name + "-" + level)

            if not os.path.isdir(output_dir):
                try:
                    os.mkdir(output_dir)
                except OSError as ex:
                    logging.error(
                        "Unable to create output directory at %s: %s",
                        output_dir, ex)
                    return None, False
            logging.info("Writing output to: %s", output_dir)

            os.chdir(output_dir)

        logging.info("------")
        logging.info("Scanning package %s (%s) at level %s", package.name,
                     package.path, level)

        issues: Dict[str, List[Issue]] = {}

        ignore_packages = self.get_ignore_packages()
        if package.name in ignore_packages:
            logging.info("Package %s is configured to be ignored by Statick.",
                         package.name)
            return issues, True

        plugin_context = PluginContext(args, self.resources, self.config)

        logging.info("---Discovery---")
        if not DiscoveryPlugin.file_command_exists():
            logging.info(
                "file command isn't available, discovery plugins will be less effective"
            )

        discovery_plugins = self.config.get_enabled_discovery_plugins(level)
        if not discovery_plugins:
            discovery_plugins = list(self.discovery_plugins.keys())
        plugins_ran: List[Any] = []
        for plugin_name in discovery_plugins:
            if plugin_name not in self.discovery_plugins:
                logging.error("Can't find specified discovery plugin %s!",
                              plugin_name)
                return None, False

            plugin = self.discovery_plugins[plugin_name]
            dependencies = plugin.get_discovery_dependencies()
            for dependency_name in dependencies:
                dependency_plugin = self.discovery_plugins[dependency_name]
                if dependency_plugin.get_name() in plugins_ran:
                    continue
                dependency_plugin.set_plugin_context(plugin_context)
                logging.info("Running %s discovery plugin...",
                             dependency_plugin.get_name())
                dependency_plugin.scan(package, level, self.exceptions)
                logging.info("%s discovery plugin done.",
                             dependency_plugin.get_name())
                plugins_ran.append(dependency_plugin.get_name())

            if plugin.get_name() not in plugins_ran:
                plugin.set_plugin_context(plugin_context)
                logging.info("Running %s discovery plugin...",
                             plugin.get_name())
                plugin.scan(package, level, self.exceptions)
                logging.info("%s discovery plugin done.", plugin.get_name())
                plugins_ran.append(plugin.get_name())
        logging.info("---Discovery---")

        logging.info("---Tools---")
        enabled_plugins = self.config.get_enabled_tool_plugins(level)
        plugins_to_run = copy.copy(enabled_plugins)
        plugins_ran = []
        plugin_dependencies: List[str] = []
        while plugins_to_run:
            plugin_name = plugins_to_run[0]

            if plugin_name not in self.tool_plugins:
                logging.error("Can't find specified tool plugin %s!",
                              plugin_name)
                return None, False

            if args.force_tool_list is not None:
                force_tool_list = args.force_tool_list.split(",")
                if (plugin_name not in force_tool_list
                        and plugin_name not in plugin_dependencies):
                    logging.info("Skipping plugin not in force list %s!",
                                 plugin_name)
                    plugins_to_run.remove(plugin_name)
                    continue

            plugin = self.tool_plugins[plugin_name]
            plugin.set_plugin_context(plugin_context)

            dependencies = plugin.get_tool_dependencies()
            dependencies_met = True
            for dependency_name in dependencies:
                if dependency_name not in plugins_ran:
                    if dependency_name not in enabled_plugins:
                        logging.error(
                            "Plugin %s depends on plugin %s which isn't enabled!",
                            plugin_name,
                            dependency_name,
                        )
                        return None, False
                    plugin_dependencies.append(dependency_name)
                    if dependency_name in plugins_to_run:
                        plugins_to_run.remove(dependency_name)
                    plugins_to_run.insert(0, dependency_name)
                    dependencies_met = False

            if not dependencies_met:
                continue

            logging.info("Running %s tool plugin...", plugin.get_name())
            tool_issues = plugin.scan(package, level)
            if tool_issues is not None:
                issues[plugin_name] = tool_issues
                logging.info("%s tool plugin done.", plugin.get_name())
            else:
                logging.error("%s tool plugin failed", plugin.get_name())
                success = False

            plugins_to_run.remove(plugin_name)
            plugins_ran.append(plugin_name)
        logging.info("---Tools---")

        if self.exceptions is not None:
            issues = self.exceptions.filter_issues(package, issues)

        os.chdir(orig_path)

        logging.info("---Reporting---")
        reporting_plugins = self.config.get_enabled_reporting_plugins(level)
        if not reporting_plugins:
            reporting_plugins = self.reporting_plugins.keys()  # type: ignore
        for plugin_name in reporting_plugins:
            if plugin_name not in self.reporting_plugins:
                logging.error("Can't find specified reporting plugin %s!",
                              plugin_name)
                return None, False

            plugin = self.reporting_plugins[plugin_name]
            plugin.set_plugin_context(plugin_context)
            logging.info("Running %s reporting plugin...", plugin.get_name())
            plugin.report(package, issues, level)
            logging.info("%s reporting plugin done.", plugin.get_name())
        logging.info("---Reporting---")
        logging.info("Done!")

        return issues, success
Пример #16
0
    def run(  # pylint: disable=too-many-locals, too-many-return-statements, too-many-branches, too-many-statements
        self, path: str, args: argparse.Namespace
    ) -> Tuple[Optional[Dict], bool]:
        """Run scan tools against targets on path."""
        success = True

        path = os.path.abspath(path)
        if not os.path.exists(path):
            print("No package found at {}!".format(path))
            return None, False

        package = Package(os.path.basename(path), path)
        level = self.get_level(path, args)  # type: Optional[str]

        assert level
        if not self.config or not self.config.has_level(level):
            print("Can't find specified level {} in config!".format(level))
            return None, False

        orig_path = os.getcwd()
        if args.output_directory:
            if not os.path.isdir(args.output_directory):
                print("Output directory not found at {}!".format(args.output_directory))
                return None, False

            output_dir = os.path.join(args.output_directory, package.name + "-" + level)

            if not os.path.isdir(output_dir):
                os.mkdir(output_dir)
            if not os.path.isdir(output_dir):
                print("Unable to create output directory at {}!".format(output_dir))
                return None, False
            print("Writing output to: {}".format(output_dir))

            os.chdir(output_dir)

        print("------")
        print(
            "Scanning package {} ({}) at level {}".format(
                package.name, package.path, level
            )
        )

        issues = {}  # type: Dict[str, List[Issue]]

        ignore_packages = self.get_ignore_packages()
        if package.name in ignore_packages:
            print(
                "Package {} is configured to be ignored by Statick.".format(
                    package.name
                )
            )
            return issues, True

        plugin_context = PluginContext(args, self.resources, self.config)

        print("---Discovery---")
        if not DiscoveryPlugin.file_command_exists():
            print(
                "file command isn't available, discovery plugins will be less effective"
            )

        discovery_plugins = self.config.get_enabled_discovery_plugins(level)
        if not discovery_plugins:
            discovery_plugins = list(self.discovery_plugins.keys())
        for plugin_name in discovery_plugins:
            if plugin_name not in self.discovery_plugins:
                print("Can't find specified discovery plugin {}!".format(plugin_name))
                return None, False

            plugin = self.discovery_plugins[plugin_name]
            plugin.set_plugin_context(plugin_context)
            print("Running {} discovery plugin...".format(plugin.get_name()))
            plugin.scan(package, level, self.exceptions)
            print("{} discovery plugin done.".format(plugin.get_name()))
        print("---Discovery---")

        print("---Tools---")
        enabled_plugins = self.config.get_enabled_tool_plugins(level)
        plugins_to_run = copy.copy(enabled_plugins)
        plugins_ran = []  # type: List[Any]
        plugin_dependencies = []  # type: List[str]
        while plugins_to_run:
            plugin_name = plugins_to_run[0]

            if plugin_name not in self.tool_plugins:
                print("Can't find specified tool plugin {}!".format(plugin_name))
                return None, False

            if args.force_tool_list is not None:
                force_tool_list = args.force_tool_list.split(",")
                if (
                    plugin_name not in force_tool_list
                    and plugin_name not in plugin_dependencies
                ):
                    print("Skipping plugin not in force list {}!".format(plugin_name))
                    plugins_to_run.remove(plugin_name)
                    continue

            plugin = self.tool_plugins[plugin_name]
            plugin.set_plugin_context(plugin_context)

            dependencies = plugin.get_tool_dependencies()
            dependencies_met = True
            for dependency_name in dependencies:
                if dependency_name not in plugins_ran:
                    if dependency_name not in enabled_plugins:
                        print(
                            "Plugin {} depends on plugin {} which isn't "
                            "enabled!".format(plugin_name, dependency_name)
                        )
                        return None, False
                    plugin_dependencies.append(dependency_name)
                    plugins_to_run.remove(dependency_name)
                    plugins_to_run.insert(0, dependency_name)
                    dependencies_met = False

            if not dependencies_met:
                continue

            print("Running {} tool plugin...".format(plugin.get_name()))
            tool_issues = plugin.scan(package, level)
            if tool_issues is not None:
                issues[plugin_name] = tool_issues
                print("{} tool plugin done.".format(plugin.get_name()))
            else:
                print("{} tool plugin failed".format(plugin.get_name()))
                success = False

            plugins_to_run.remove(plugin_name)
            plugins_ran.append(plugin_name)
        print("---Tools---")

        if self.exceptions is not None:
            issues = self.exceptions.filter_issues(package, issues)

        os.chdir(orig_path)

        print("---Reporting---")
        reporting_plugins = self.config.get_enabled_reporting_plugins(level)
        if not reporting_plugins:
            reporting_plugins = self.reporting_plugins.keys()  # type: ignore
        for plugin_name in reporting_plugins:
            if plugin_name not in self.reporting_plugins.keys():
                print("Can't find specified reporting plugin {}!".format(plugin_name))
                return None, False

            plugin = self.reporting_plugins[plugin_name]
            plugin.set_plugin_context(plugin_context)
            print("Running {} reporting plugin...".format(plugin.get_name()))
            plugin.report(package, issues, level)
            print("{} reporting plugin done.".format(plugin.get_name()))
        print("---Reporting---")
        print("Done!")

        return issues, success
Пример #17
0
def test_discovery_plugin_no_file_cmd():
    """Test when file command does not exist."""
    with modified_environ(PATH=""):
        dp = DiscoveryPlugin()
        assert not dp.file_command_exists()
Пример #18
0
def test_discovery_plugin_file_cmd_exists():
    """Test when file command exists."""
    dp = DiscoveryPlugin()
    assert dp.file_command_exists()