diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/requirements.txt b/tests/requirements.txt deleted file mode 100644 index deb72d7d..00000000 --- a/tests/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -future -pandas -xlrd \ No newline at end of file diff --git a/tests/test__get_input.py b/tests/test__get_input.py new file mode 100644 index 00000000..68b90bb6 --- /dev/null +++ b/tests/test__get_input.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2020 LG Electronics Inc. +# SPDX-License-Identifier: Apache-2.0 + + +from fosslight_scanner._get_input import get_input, get_input_mode + + +def test_get_input(monkeypatch): + # given + ask_msg = "Please enter the path to analyze:" + default_value = "default" + + # when + # Mock input to return an empty string + monkeypatch.setattr('builtins.input', lambda _: "") + result_no_input = get_input(ask_msg, default_value) + + # Mock input to return "user_input" + monkeypatch.setattr('builtins.input', lambda _: "user_input") + result_with_input = get_input(ask_msg, "user_input") + + # then + assert result_no_input == "default" + assert result_with_input == "user_input" + + +def test_get_input_mode(monkeypatch): + # given + executed_path = "" + mode_list = ["all", "dep"] + + # Mock ask_to_run to return a predetermined input value + monkeypatch.setattr('fosslight_scanner._get_input.ask_to_run', lambda _: "1") + + # Mock input to provide other necessary return values + monkeypatch.setattr('builtins.input', lambda _: "https://example.com") + + # when + _, _, url_to_analyze = get_input_mode(executed_path, mode_list) + + # then + assert url_to_analyze == "https://example.com" diff --git a/tests/test__help.py b/tests/test__help.py new file mode 100644 index 00000000..d4abf38a --- /dev/null +++ b/tests/test__help.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2020 LG Electronics Inc. +# SPDX-License-Identifier: Apache-2.0 + + +import sys +from fosslight_scanner._help import print_help_msg, _HELP_MESSAGE_SCANNER + + +def test_print_help_msg(capsys, monkeypatch): + # given + # monkeypatch sys.exit to prevent the test from stopping + monkeypatch.setattr(sys, "exit", lambda: None) + + # when + print_help_msg() + + # then + captured = capsys.readouterr() + # Validate the help message output + assert _HELP_MESSAGE_SCANNER.strip() in captured.out diff --git a/tests/test__parse_setting.py b/tests/test__parse_setting.py new file mode 100644 index 00000000..5a0f82f2 --- /dev/null +++ b/tests/test__parse_setting.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2020 LG Electronics Inc. +# SPDX-License-Identifier: Apache-2.0 + +from fosslight_scanner._parse_setting import parse_setting_json + + +def test_parse_setting_json_valid_data(): + data = { + 'mode': ['test'], + 'path': ['/some/path'], + 'dep_argument': 'arg', + 'output': 'output', + 'format': 'json', + 'link': 'http://example.com', + 'db_url': 'sqlite:///:memory:', + 'timer': True, + 'raw': True, + 'core': 4, + 'no_correction': True, + 'correct_fpath': '/correct/path', + 'ui': True, + 'exclude': ['/exclude/path'], + 'selected_source_scanner': 'scanner', + 'source_write_json_file': True, + 'source_print_matched_text': True, + 'source_time_out': 60, + 'binary_simple': True + } + result = parse_setting_json(data) + assert result == ( + ['test'], ['/some/path'], 'arg', 'output', 'json', 'http://example.com', 'sqlite:///:memory:', True, + True, 4, True, '/correct/path', True, ['/exclude/path'], 'scanner', True, True, 60, True + ) diff --git a/tests/test__run_compare.py b/tests/test__run_compare.py new file mode 100644 index 00000000..4b1bd057 --- /dev/null +++ b/tests/test__run_compare.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2020 LG Electronics Inc. +# SPDX-License-Identifier: Apache-2.0 + + +import pytest +from fosslight_scanner._run_compare import write_result_json_yaml, parse_result_for_table, get_sample_html, \ + write_result_html, write_result_xlsx, write_compared_result, get_comparison_result_filename, \ + count_compared_result, run_compare, \ + ADD, DELETE, CHANGE, XLSX_EXT, HTML_EXT, YAML_EXT, JSON_EXT +import logging +import json +import yaml + + +@pytest.mark.parametrize("ext, expected_content", [ + # Test for JSON and YAML extensions + (".json", {"key": "value"}), + (".yaml", {"key": "value"}), + + # Test for TXT extension (failure) + (".txt", {"key": "value"}), +]) +def test_write_result_json_yaml(tmp_path, ext, expected_content): + # given + output_file = tmp_path / f"result{ext}" + compared_result = expected_content + + # when + success = write_result_json_yaml(output_file, compared_result, ext) + + # then + assert success is True, f"Failed to write file with extension {ext}" + + # Verify content based on extension + if ext == ".json": + with open(output_file, 'r', encoding='utf-8') as f: + result_content = json.load(f) + assert result_content == compared_result, "The content of the JSON file does not match the expected content." + + elif ext == ".yaml": + with open(output_file, 'r', encoding='utf-8') as f: + result_content = yaml.safe_load(f) + assert result_content == compared_result, "The content of the YAML file does not match the expected content." + + elif ext == ".txt": + with open(output_file, 'r', encoding='utf-8') as f: + result_lines = f.readlines() + result_content = ''.join(result_lines) + assert result_content != compared_result, "The content of the TXT file does not match the expected string representation." + + +def test_parse_result_for_table(): + # given + add_expected = [ADD, '', '', 'test(1.0)', 'MIT'] + oi = {"name": "test", "version": "1.0", "license": ["MIT"]} + + # when + add_result = parse_result_for_table(oi, ADD) + + # then + assert add_result == add_expected + + +def test_get_sample_html(): + # then + assert get_sample_html() != '' + + +@pytest.mark.parametrize("compared_result, expected_before, expected_after", [ + # Case with empty add, delete, change + ({ADD: [], DELETE: [], CHANGE: []}, "before.yaml", "after.yaml"), + + # Case with one entry in add and no deletes or changes + ({ADD: [{"name": "test", "version": "1.0", "license": ["MIT"]}], DELETE: [], CHANGE: []}, + "before.yaml", "after.yaml") +]) +def test_write_result_html(tmp_path, compared_result, expected_before, expected_after): + # given + output_file = tmp_path / "result.html" + + # when + success = write_result_html(output_file, compared_result, expected_before, expected_after) + + # then + assert success is True, "Failed to write the HTML file." + assert output_file.exists(), "The HTML file was not created." + with open(output_file, 'r', encoding='utf-8') as f: + content = f.read() + assert content, "The HTML file is empty." + + +@pytest.mark.parametrize("compared_result", [ + # Case with empty add, delete, change + {ADD: [], DELETE: [], CHANGE: []}, + + # Case with one entry in add and no deletes or changes + {ADD: [{"name": "test", "version": "1.0", "license": ["MIT"]}], DELETE: [], CHANGE: []} +]) +def test_write_result_xlsx(tmp_path, compared_result): + # given + output_file = tmp_path / "result.xlsx" + + # when + success = write_result_xlsx(output_file, compared_result) + + # then + assert success is True, "Failed to write the XLSX file." + assert output_file.exists(), "The XLSX file was not created." + + +@pytest.mark.parametrize("ext, expected_output", [ + (XLSX_EXT, "xlsx"), + (HTML_EXT, "html"), + (JSON_EXT, "json"), + (YAML_EXT, "yaml"), +]) +def test_write_compared_result(tmp_path, ext, expected_output): + # given + output_file = tmp_path / "result" + compared_result = {ADD: [], DELETE: [], CHANGE: []} + + # when + success, result_file = write_compared_result(output_file, compared_result, ext) + + # then + assert success is True, f"Failed to write the compared result for extension {ext}" + if ext == XLSX_EXT: + assert str(result_file) == str(output_file), "The XLSX result file path does not match the expected output path." + elif ext == HTML_EXT: + expected_result_file = f"{str(output_file) + XLSX_EXT}, {str(output_file)}" + assert result_file == expected_result_file, "HTML file creation failed." + elif ext == JSON_EXT: + assert str(result_file) == str(output_file), "The JSON result file path does not match the expected output path." + else: + assert str(result_file) == str(output_file), "The YAML result file path does not match the expected output path." + + +@pytest.mark.parametrize("path, file_name, ext, time_suffix, expected_output", [ + # Case when file name is provided + ("/path", "file", XLSX_EXT, "time", "/path/file.xlsx"), + + # Case when file name is empty, with different extensions + ("/path", "", XLSX_EXT, "time", "/path/fosslight_compare_time.xlsx"), + ("/path", "", HTML_EXT, "time", "/path/fosslight_compare_time.html"), + ("/path", "", YAML_EXT, "time", "/path/fosslight_compare_time.yaml"), + ("/path", "", JSON_EXT, "time", "/path/fosslight_compare_time.json"), +]) +def test_get_comparison_result_filename(path, file_name, ext, time_suffix, expected_output): + # when + result = get_comparison_result_filename(path, file_name, ext, time_suffix) + + # then + assert result == expected_output, f"Expected {expected_output} but got {result}" + + +@pytest.mark.parametrize("compared_result, expected_log", [ + ({ADD: [], DELETE: [], CHANGE: []}, "all oss lists are the same."), + ({ADD: [{"name": "test"}], DELETE: [], CHANGE: []}, "total 1 oss updated (add: 1, delete: 0, change: 0)") +]) +def test_count_compared_result(compared_result, expected_log, caplog): + # when + with caplog.at_level(logging.INFO): + count_compared_result(compared_result) + # then + assert expected_log in caplog.text + + +def test_run_compare_different_extension(tmp_path): + # given + before_f = tmp_path / "before.yaml" + after_f = tmp_path / "after.xlsx" + output_path = tmp_path + output_file = "result" + file_ext = ".yaml" + _start_time = "time" + _output_dir = tmp_path + + # Write example content to before_f and after_f + before_content = { + "oss_list": [ + {"name": "test", "version": "1.0", "license": "MIT"} + ] + } + + # Write these contents to the files + with open(before_f, "w") as bf: + yaml.dump(before_content, bf) + + # Create an empty xlsx file for after_f + with open(after_f, "w") as af: + af.write("") + + # when + comparison_result = run_compare(before_f, after_f, output_path, output_file, file_ext, _start_time, _output_dir) + + # then + assert comparison_result is False diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..f3d47df5 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2020 LG Electronics Inc. +# SPDX-License-Identifier: Apache-2.0 + +import pytest +import json +import sys +from fosslight_scanner.cli import main, set_args + + +def test_set_args(monkeypatch): + # Mocking os.path.isfile to return True + monkeypatch.setattr("os.path.isfile", lambda x: True) + + # Mocking the open function to return a mock file object + mock_file_data = json.dumps({ + "mode": ["test_mode"], + "path": ["test_path"], + "dep_argument": "test_dep_argument", + "output": "test_output", + "format": "test_format", + "link": "test_link", + "db_url": "test_db_url", + "timer": True, + "raw": True, + "core": 4, + "no_correction": True, + "correct_fpath": "test_correct_fpath", + "ui": True, + "exclude": ["test_exclude_path"], + "selected_source_scanner": "test_scanner", + "source_write_json_file": True, + "source_print_matched_text": True, + "source_time_out": 100, + "binary_simple": True + }) + + def mock_open(*args, **kwargs): + from io import StringIO + return StringIO(mock_file_data) + + monkeypatch.setattr("builtins.open", mock_open) + + # Call the function with some arguments + result = set_args( + mode=None, path=None, dep_argument=None, output=None, format=None, link=None, db_url=None, timer=None, + raw=None, core=-1, no_correction=None, correct_fpath=None, ui=None, setting="dummy_path", exclude_path=None + ) + + # Expected result + expected = ( + ["test_mode"], ["test_path"], "test_dep_argument", "test_output", "test_format", "test_link", "test_db_url", True, + True, 4, True, "test_correct_fpath", True, ["test_exclude_path"], "test_scanner", True, True, 100, True + ) + + assert result == expected + + +def test_main_invalid_option(capsys): + # given + test_args = ["fosslight_scanner", "--invalid_option"] + sys.argv = test_args + + # when + with pytest.raises(SystemExit): # 예상되는 SystemExit 처리 + main() + + # then + captured = capsys.readouterr() + assert "unrecognized arguments" in captured.err # 인식되지 않은 인자에 대한 에러 메시지 확인 diff --git a/tests/test_common.py b/tests/test_common.py new file mode 100644 index 00000000..afc9d4ec --- /dev/null +++ b/tests/test_common.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2020 LG Electronics Inc. +# SPDX-License-Identifier: Apache-2.0 + + +import os +import pytest +from fosslight_scanner.common import copy_file, run_analysis, call_analysis_api, check_exclude_dir, \ + create_scancodejson, correct_scanner_result + + +def test_copy_file_success(tmp_path): + # given + source = tmp_path / "source.txt" + destination = tmp_path / "destination" + source.write_text("Test content") + + # when + success, copied_file = copy_file(str(source), str(destination)) + + # then + assert success is True + assert os.path.exists(copied_file) + + +def test_copy_file_failure(): + # given + source = "/nonexistent/path/source.txt" + destination = "/destination/path" + + # when + success, _ = copy_file(source, destination) + + # then + assert success is False + + +@pytest.mark.parametrize("path_to_run, expected_result", [ + ("/test/path", "Mocked result"), + ("", "") +]) +def test_run_analysis(monkeypatch, path_to_run, expected_result): + # given + params = ["--param1", "value1"] + output = "/output/path" + exe_path = "/exe/path" + + def mock_func(): + return expected_result + + # Mock os.chdir to prevent changing directories during test + monkeypatch.setattr(os, 'chdir', lambda x: None) + # Mock os.getcwd to return a default path + monkeypatch.setattr(os, 'getcwd', lambda: "/mocked/path") + + # when + result = run_analysis(path_to_run, params, mock_func, "Test Run", output, exe_path) + + # then + assert result == expected_result + + +def test_call_analysis_api_with_path(): + # given + path_to_run = "/test/path" + str_run_start = "Test Run" + return_idx = -1 + + def mock_func(): + return ["Result"] + + # when + success, result = call_analysis_api(path_to_run, str_run_start, return_idx, mock_func) + + # then + assert success is True + assert result == ["Result"] + + +def test_call_analysis_api_without_path(): + # given + path_to_run = "" + str_run_start = "Test Run" + return_idx = -1 + + def mock_func(): + return ["Result"] + + # when + success, result = call_analysis_api(path_to_run, str_run_start, return_idx, mock_func) + + # then + assert success is True + assert result == [] + + +def test_create_scancodejson(monkeypatch): + # Given + + # Mocking os.walk + def mock_os_walk(path): + return [("/mocked/absolute/path", ["dir1"], ["file1", "file2"])] + + # Mocking write_scancodejson + def mock_write_scancodejson(dirname, basename, all_scan_item): + pass + + # Mocking copy.deepcopy + def mock_deepcopy(obj): + return obj + + # Mocking os.path.abspath + monkeypatch.setattr("os.path.abspath", lambda x: "/mocked/absolute/path") + + # Mocking os.path.basename + monkeypatch.setattr("os.path.basename", lambda x: "mocked_parent") + monkeypatch.setattr("os.walk", mock_os_walk) + monkeypatch.setattr("fosslight_scanner.common.write_scancodejson", mock_write_scancodejson) + monkeypatch.setattr("copy.deepcopy", mock_deepcopy) + + # Mocking FOSSLIGHT_DEPENDENCY and FOSSLIGHT_SOURCE + global FOSSLIGHT_DEPENDENCY, FOSSLIGHT_SOURCE + FOSSLIGHT_DEPENDENCY = "fosslight_dependency" + FOSSLIGHT_SOURCE = "fosslight_source" + + # Mocking all_scan_item_origin + class AllScanItem: + def __init__(self): + self.file_items = { + FOSSLIGHT_DEPENDENCY: [], + FOSSLIGHT_SOURCE: [] + } + + all_scan_item_origin = AllScanItem() + ui_mode_report = "/mocked/ui_mode_report" + src_path = "/mocked/src_path" + + # When + success, err_msg = create_scancodejson(all_scan_item_origin, ui_mode_report, src_path) + + # Then + assert success is True + assert err_msg == '' + + +def test_correct_scanner_result(monkeypatch): + # Given + class MockOSSItem: + def __init__(self, license, exclude=False): + self.license = license + self.exclude = exclude + + class MockFileItem: + def __init__(self, source_name_or_path, oss_items, exclude=False): + self.source_name_or_path = source_name_or_path + self.oss_items = oss_items + self.exclude = exclude + + class MockAllScanItem: + def __init__(self, file_items): + self.file_items = file_items + + src_oss_item = MockOSSItem(license="MIT") + bin_oss_item = MockOSSItem(license="") + + src_file_item = MockFileItem("path/to/source", [src_oss_item]) + bin_file_item = MockFileItem("path/to/source", [bin_oss_item]) + + all_scan_item = MockAllScanItem({ + "FOSSLIGHT_SOURCE": [src_file_item], + "FOSSLIGHT_BINARY": [bin_file_item] + }) + + def mock_check_exclude_dir(source_name_or_path, file_item_exclude): + return file_item_exclude + + monkeypatch.setattr("fosslight_scanner.common.check_exclude_dir", mock_check_exclude_dir) + # When + result = correct_scanner_result(all_scan_item) + # Then + assert len(result.file_items["FOSSLIGHT_BINARY"][0].oss_items) == 1 + + +@pytest.mark.parametrize("source_name_or_path, file_item_exclude, expected", [ + ("project/venv/file.py", False, True), + ("project/node_modules/file.js", False, True), + ("project/Pods/file.m", False, True), + ("project/Carthage/file.swift", False, True), + ("project/src/file.py", False, False), + ("project/src/file.py", True, True), + ("project/venv/file.py", True, True), +]) +def test_check_exclude_dir(source_name_or_path, file_item_exclude, expected): + assert check_exclude_dir(source_name_or_path, file_item_exclude) == expected diff --git a/tests/test_fosslight_scanner.py b/tests/test_fosslight_scanner.py new file mode 100644 index 00000000..b13ecd0d --- /dev/null +++ b/tests/test_fosslight_scanner.py @@ -0,0 +1,150 @@ +from fosslight_scanner.fosslight_scanner import run_scanner, download_source, init, run_main, run_dependency +from fosslight_util.oss_item import ScannerItem + + +def test_run_dependency(tmp_path): + # given + path_to_analyze = tmp_path / "test_project" + output_file_with_path = tmp_path / "output_file" + params = "-m 'npm' -a 'activate_cmd' -d 'deactivate_cmd' -c 'custom_dir' -n 'app_name' -t 'token_value'" + path_to_exclude = ["node_modules"] + + # Create the directory to analyze + path_to_analyze.mkdir(parents=True, exist_ok=True) + + # when + result = run_dependency(str(path_to_analyze), str(output_file_with_path), params, path_to_exclude) + + # then + # Check that result is an instance of ScannerItem + assert isinstance(result, ScannerItem), "Result should be an instance of ScannerItem." + + # Check that result is not None + assert result is not None, "Result should not be None." + + +def test_run_scanner(tmp_path): + # given + src_path = tmp_path / "test_src" + output_path = tmp_path / "output" + dep_arguments = "" + output_file = "test_output" + output_extension = ".yaml" + + # Create necessary directories and files for the test + src_path.mkdir(parents=True, exist_ok=True) + output_path.mkdir(parents=True, exist_ok=True) + + # Create a dummy source file in `src_path` to be scanned + test_file = src_path / "test_file.py" + test_file.write_text("# This is a test file\nprint('Hello, World!')") + + # when + run_scanner( + src_path=str(src_path), + dep_arguments=dep_arguments, + output_path=str(output_path), + keep_raw_data=True, + run_src=True, + run_bin=False, + run_dep=False, + run_prechecker=False, + remove_src_data=False, + result_log={}, + output_file=output_file, + output_extension=output_extension, + num_cores=1, + path_to_exclude=[] + ) + + # then + # Check if `src_path` and `output_path` still exist + assert src_path.is_dir(), "Source path should still exist." + assert output_path.is_dir(), "Output path should still exist." + + +def test_download_source(tmp_path): + # given + link = "https://example.com/test_repo.git" + out_dir = tmp_path / "output" + + # Create the necessary output directory + out_dir.mkdir(parents=True, exist_ok=True) + + # when + success, temp_src_dir, oss_name, oss_version = download_source(str(link), str(out_dir)) + + # then + # Ensure the function completes successfully + assert isinstance(success, bool), "Function should return a boolean for success." + + # If the function fails, ensure temp_src_dir is empty + if not success: + assert temp_src_dir == "", "temp_src_dir should be an empty string if download fails." + else: + # If the function succeeds, check temp_src_dir is a valid path + assert isinstance(temp_src_dir, str), "Temporary source directory should be a string." + assert str(temp_src_dir).startswith(str(out_dir)), "Temporary source directory should be within the output directory." + + # Ensure oss_name and oss_version are strings + assert isinstance(oss_name, str), "OSS name should be a string." + assert isinstance(oss_version, str), "OSS version should be a string." + + +def test_init(tmp_path): + # given + output_path = tmp_path / "output" + + # Initialize global variables if necessary + global _output_dir, _log_file + _output_dir = "test_output" # Set to a default or test-specific value + _log_file = "test_log" # Set the name of the log file + + # when + dir_created, output_root_dir, result_log = init(str(output_path)) + + # then + # Ensure that the output directory was created + assert dir_created is True, "The output directory should be created." + + # Check if the output_root_dir is correct + assert output_root_dir == str(output_path), "Output root directory should match the given path." + + # Check that the result_log is not None and is a dictionary + assert result_log is not None, "Result log should not be None." + assert isinstance(result_log, dict), "Result log should be a dictionary." + + +def test_run_main(tmp_path): + # given + mode_list = ["source"] + path_arg = [str(tmp_path / "test_src")] + dep_arguments = [] + output_file_or_dir = str(tmp_path / "output") + file_format = "yaml" + url_to_analyze = "" + db_url = "" + + # Create necessary directories and files for the test + (tmp_path / "test_src").mkdir(parents=True, exist_ok=True) + + # when + result = run_main( + mode_list=mode_list, + path_arg=path_arg, + dep_arguments=dep_arguments, + output_file_or_dir=output_file_or_dir, + file_format=file_format, + url_to_analyze=url_to_analyze, + db_url=db_url, + hide_progressbar=True, # Disable progress bar for testing + keep_raw_data=True, # Keep raw data to avoid cleanup during test + num_cores=1, + correct_mode=True, + correct_fpath="", + ui_mode=False, + path_to_exclude=[] + ) + + # then + assert result is True diff --git a/tox.ini b/tox.ini index 61f92a09..c2dec79c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,48 +1,46 @@ -# Copyright (c) 2021 LG Electronics -# SPDX-License-Identifier: Apache-2.0 + #!/usr/bin/env python + # -*- coding: utf-8 -*- + # Copyright (c) 2020 LG Electronics Inc. + # SPDX-License-Identifier: Apache-2.0 + [tox] envlist = test_run skipdist = true [testenv] install_command = pip install {opts} {packages} -whitelist_externals = cat +allowlist_externals = cat cp rm ls + pytest setenv = - PYTHONPATH=. + PYTHONPATH={toxinidir} [flake8] max-line-length = 130 exclude = .tox/* [pytest] -filterwarnings = ignore::DeprecationWarning +testpaths = tests norecursedirs = test_result_* [testenv:test_run] +deps = + -r{toxinidir}/requirements-dev.txt + +changedir = {toxinidir}/tests + commands = - rm -rf test_result_local_path - rm -rf test_result_exclude_path - rm -rf test_result_wget - fosslight -o test_result_local_path/test.xlsx -p tests -r - fosslight binary source -o test_result_multi_mode/test.xlsx -p tests -r - fosslight -o test_result_exclude_path/test.xlsx -p tests -e test sample_license.txt - fosslight dependency -o test_result_wget -w "https://github.com/LGE-OSS/example.git" - fosslight source -s tests/setting.json - ls test_result_wget + pytest + [testenv:release] deps = -r{toxinidir}/requirements-dev.txt +changedir = {toxinidir}/tests + commands = - fosslight -h - fosslight all -o test_result_local_path/test.xlsx -p tests -r - fosslight binary dependency -o test_result_multi_mode/test.xlsx -p tests -r - fosslight -o test_result_exclude_path/test.xlsx -p tests -e test sample_license.txt - fosslight source -o test_result_wget -w "https://github.com/LGE-OSS/example.git" - fosslight binary -s tests/setting.json pytest -v --flake8