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()
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
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)
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)
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)
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
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)
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}")
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
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
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
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
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
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
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
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
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()
def test_discovery_plugin_file_cmd_exists(): """Test when file command exists.""" dp = DiscoveryPlugin() assert dp.file_command_exists()