diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a00ee8dc7..e6cb99bfc 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,7 +14,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [ubuntu-latest, macos-latest]
+ os: [ubuntu-latest, macos-latest, windows-latest]
python: ['3.6', '3.7', '3.8', '3.9']
steps:
- uses: actions/checkout@v2
diff --git a/testing/mocks.py b/testing/mocks.py
index b97a38cb2..a85325651 100644
--- a/testing/mocks.py
+++ b/testing/mocks.py
@@ -1,5 +1,7 @@
"""This is a collection of utility functions for easier, DRY testing."""
import io
+import os
+import tempfile
from collections import defaultdict
from contextlib import contextmanager
from types import ModuleType
@@ -88,3 +90,22 @@ def disable_gibberish_filter() -> Iterator[None]:
return_value=False,
):
yield
+
+
+@contextmanager
+def mock_named_temporary_file(
+ mode: str = 'w+b', dir: str = None,
+ suffix: str = None, prefix: str = None,
+) -> Iterator[IO[Any]]:
+ """
+ Used to create a mock temporary named file to write baseline files and secret files in
+ test. To avoid platform differences on how "NamedTemporaryFile" operates, we will perform
+ the creation and cleanup of the temporary file here.
+ """
+ with tempfile.NamedTemporaryFile(
+ mode=mode, dir=dir, suffix=suffix, prefix=prefix, delete=False,
+ ) as f:
+ yield f
+
+ f.close()
+ os.unlink(f.name)
diff --git a/tests/audit/analytics_test.py b/tests/audit/analytics_test.py
index 156a1354c..3938ce2fd 100644
--- a/tests/audit/analytics_test.py
+++ b/tests/audit/analytics_test.py
@@ -1,7 +1,6 @@
import json
import random
import string
-import tempfile
from contextlib import contextmanager
import pytest
@@ -11,6 +10,7 @@
from detect_secrets.main import main
from detect_secrets.plugins.basic_auth import BasicAuthDetector
from testing.factories import potential_secret_factory as original_potential_secret_factory
+from testing.mocks import mock_named_temporary_file
def potential_secret_factory(**kwargs):
@@ -59,7 +59,7 @@ def test_basic_statistics_json(printer):
def test_no_divide_by_zero(secret):
secrets = SecretsCollection()
secrets['file'].add(secret)
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
baseline.save_to_file(secrets, f.name)
f.seek(0)
@@ -84,7 +84,7 @@ def labelled_secrets():
potential_secret_factory(is_secret=False),
}
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
baseline.save_to_file(secrets, f.name)
f.seek(0)
diff --git a/tests/audit/audit_test.py b/tests/audit/audit_test.py
index b71d5d03d..46099c961 100644
--- a/tests/audit/audit_test.py
+++ b/tests/audit/audit_test.py
@@ -1,6 +1,5 @@
import json
import random
-import tempfile
from typing import List
from typing import Optional
from unittest import mock
@@ -12,6 +11,7 @@
from detect_secrets.main import main
from detect_secrets.settings import transient_settings
from testing.factories import potential_secret_factory
+from testing.mocks import mock_named_temporary_file
def test_nothing_to_audit(printer):
@@ -166,7 +166,7 @@ def run_logic(
:param input: if provided, will automatically quit at the end of input string.
otherwise, will assert that no user input is requested.
"""
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
baseline.save_to_file(secrets, f.name)
f.seek(0)
diff --git a/tests/audit/compare_test.py b/tests/audit/compare_test.py
index 893ac6a4f..7eeace197 100644
--- a/tests/audit/compare_test.py
+++ b/tests/audit/compare_test.py
@@ -1,5 +1,4 @@
import re
-import tempfile
from contextlib import contextmanager
from unittest import mock
@@ -11,6 +10,7 @@
from detect_secrets.main import main
from detect_secrets.plugins.basic_auth import BasicAuthDetector
from testing.factories import potential_secret_factory as original_potential_secret_factory
+from testing.mocks import mock_named_temporary_file
def potential_secret_factory(secret: str, **kwargs):
@@ -139,7 +139,7 @@ def test_fails_when_no_line_number(printer):
def run_logic(secretsA: SecretsCollection, secretsB: SecretsCollection):
- with tempfile.NamedTemporaryFile() as f, tempfile.NamedTemporaryFile() as g:
+ with mock_named_temporary_file() as f, mock_named_temporary_file() as g:
baseline.save_to_file(secretsA, f.name)
baseline.save_to_file(secretsB, g.name)
diff --git a/tests/audit/report_test.py b/tests/audit/report_test.py
index d544d21e1..abdef414f 100644
--- a/tests/audit/report_test.py
+++ b/tests/audit/report_test.py
@@ -1,6 +1,5 @@
import random
import string
-import tempfile
import textwrap
from contextlib import contextmanager
@@ -14,6 +13,7 @@
from detect_secrets.plugins.basic_auth import BasicAuthDetector
from detect_secrets.plugins.jwt import JwtTokenDetector
from detect_secrets.settings import transient_settings
+from testing.mocks import mock_named_temporary_file
url_format = 'http://username:{}@www.example.com/auth'
@@ -166,7 +166,7 @@ def count_results(data):
@contextmanager
def create_file_with_content(content):
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
f.write(content.encode())
f.seek(0)
yield f.name
@@ -187,7 +187,7 @@ def baseline_file():
with create_file_with_content(first_content) as first_file, \
create_file_with_content(second_content) as second_file, \
- tempfile.NamedTemporaryFile() as baseline_file, \
+ mock_named_temporary_file() as baseline_file, \
transient_settings({
'plugins_used': [
{'name': 'BasicAuthDetector'},
diff --git a/tests/core/baseline_test.py b/tests/core/baseline_test.py
index 7fbefbacd..c8e24a8f3 100644
--- a/tests/core/baseline_test.py
+++ b/tests/core/baseline_test.py
@@ -1,6 +1,7 @@
import json
import subprocess
import tempfile
+from pathlib import Path
from unittest import mock
import pytest
@@ -8,6 +9,7 @@
from detect_secrets.core import baseline
from detect_secrets.settings import get_settings
from detect_secrets.util.path import get_relative_path_if_in_cwd
+from testing.mocks import mock_named_temporary_file
@pytest.fixture(autouse=True)
@@ -39,8 +41,8 @@ def test_basic_usage(path):
secrets = baseline.create(path)
assert len(secrets.data.keys()) == 2
- assert len(secrets['test_data/files/file_with_secrets.py']) == 1
- assert len(secrets['test_data/files/tmp/file_with_secrets.py']) == 2
+ assert len(secrets[str(Path('test_data/files/file_with_secrets.py'))]) == 1
+ assert len(secrets[str(Path('test_data/files/tmp/file_with_secrets.py'))]) == 2
@staticmethod
def test_error_when_getting_git_tracked_files():
@@ -59,7 +61,7 @@ def test_non_existent_file():
def test_no_files_in_git_repo():
with tempfile.TemporaryDirectory() as d:
# Create a new directory, so scanning is sandboxed.
- with tempfile.NamedTemporaryFile(dir=d, suffix='.py') as f:
+ with mock_named_temporary_file(dir=d, suffix='.py') as f:
f.write(b'"2b00042f7481c7b056c4b410d28f33cf"')
f.seek(0)
@@ -69,7 +71,7 @@ def test_no_files_in_git_repo():
@staticmethod
def test_scan_all_files():
- with tempfile.NamedTemporaryFile(dir='test_data/files/tmp', suffix='.py') as f:
+ with mock_named_temporary_file(dir='test_data/files/tmp', suffix='.py') as f:
f.write(b'"2b00042f7481c7b056c4b410d28f33cf"')
f.seek(0)
diff --git a/tests/core/scan_test.py b/tests/core/scan_test.py
index 279f8e4e0..566e2e463 100644
--- a/tests/core/scan_test.py
+++ b/tests/core/scan_test.py
@@ -1,6 +1,6 @@
import os
-import tempfile
import textwrap
+from pathlib import Path
import pytest
@@ -8,6 +8,7 @@
from detect_secrets.settings import transient_settings
from detect_secrets.util import git
from detect_secrets.util.path import get_relative_path_if_in_cwd
+from testing.mocks import mock_named_temporary_file
class TestGetFilesToScan:
@@ -53,16 +54,16 @@ def test_handles_each_path_separately(non_tracked_file):
@staticmethod
def test_handles_multiple_directories():
- directories = ['test_data/short_files', 'test_data/files']
+ directories = [Path('test_data/short_files'), Path('test_data/files')]
results = list(scan.get_files_to_scan(*directories))
for prefix in directories:
- assert len(list(filter(lambda x: x.startswith(prefix), results))) > 1
+ assert len(list(filter(lambda x: x.startswith(str(prefix)), results))) > 1
@staticmethod
@pytest.fixture(autouse=True, scope='class')
def non_tracked_file():
- with tempfile.NamedTemporaryFile(
+ with mock_named_temporary_file(
prefix=os.path.join(git.get_root_directory(), 'test_data/'),
) as f:
f.write(b'content does not matter')
@@ -74,7 +75,7 @@ def non_tracked_file():
class TestScanFile:
@staticmethod
def test_handles_broken_yaml_gracefully():
- with tempfile.NamedTemporaryFile(suffix='.yaml') as f:
+ with mock_named_temporary_file(suffix='.yaml') as f:
f.write(
textwrap.dedent("""
metadata:
@@ -89,7 +90,7 @@ def test_handles_broken_yaml_gracefully():
def test_handles_binary_files_gracefully():
# NOTE: This suffix needs to be something that isn't in the known file types, as determined
# by `detect_secrets.util.filetype.determine_file_type`.
- with tempfile.NamedTemporaryFile(suffix='.woff2') as f:
+ with mock_named_temporary_file(suffix='.woff2') as f:
f.write(b'\x86')
f.seek(0)
diff --git a/tests/core/usage/baseline_usage_test.py b/tests/core/usage/baseline_usage_test.py
index 39db8a76e..c680488d3 100644
--- a/tests/core/usage/baseline_usage_test.py
+++ b/tests/core/usage/baseline_usage_test.py
@@ -1,5 +1,4 @@
import json
-import tempfile
from contextlib import contextmanager
import pytest
@@ -7,6 +6,7 @@
from detect_secrets.core.plugins.util import get_mapping_from_secret_type_to_class
from detect_secrets.core.usage import ParserBuilder
from detect_secrets.settings import get_settings
+from testing.mocks import mock_named_temporary_file
@pytest.fixture
@@ -60,7 +60,7 @@ def test_success(parser):
@contextmanager
def _mock_file(content: str):
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
f.write(content.encode())
f.seek(0)
diff --git a/tests/core/usage/filters_usage_test.py b/tests/core/usage/filters_usage_test.py
index f7e0a4c28..c4836d9b5 100644
--- a/tests/core/usage/filters_usage_test.py
+++ b/tests/core/usage/filters_usage_test.py
@@ -1,4 +1,3 @@
-import tempfile
import uuid
import pytest
@@ -10,11 +9,12 @@
from detect_secrets.settings import default_settings
from detect_secrets.settings import get_settings
from detect_secrets.settings import transient_settings
+from testing.mocks import mock_named_temporary_file
def test_no_verify_overrides_baseline_settings(parser):
secrets = SecretsCollection()
- with tempfile.NamedTemporaryFile() as f, transient_settings({
+ with mock_named_temporary_file() as f, transient_settings({
'filters_used': [{
'path': 'detect_secrets.filters.common.is_ignored_due_to_verification_policies',
'min_level': VerifiedResult.UNVERIFIED.value,
@@ -30,7 +30,7 @@ def test_no_verify_overrides_baseline_settings(parser):
def test_only_verified_overrides_baseline_settings(parser):
secrets = SecretsCollection()
- with tempfile.NamedTemporaryFile() as f, transient_settings({
+ with mock_named_temporary_file() as f, transient_settings({
'filters_used': [{
'path': 'detect_secrets.filters.common.is_ignored_due_to_verification_policies',
'min_level': VerifiedResult.UNVERIFIED.value,
@@ -134,7 +134,7 @@ def test_module_failure(parser, filepath):
def test_disable_filter(parser):
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
f.write(f'secret = "{uuid.uuid4()}"'.encode())
# First, make sure that we actually catch it.
diff --git a/tests/core/usage/plugins_usage_test.py b/tests/core/usage/plugins_usage_test.py
index f1e12dbcc..f7bc21b81 100644
--- a/tests/core/usage/plugins_usage_test.py
+++ b/tests/core/usage/plugins_usage_test.py
@@ -1,6 +1,5 @@
import json
import os
-import tempfile
import pytest
@@ -9,6 +8,7 @@
from detect_secrets.core.secrets_collection import SecretsCollection
from detect_secrets.core.usage import ParserBuilder
from detect_secrets.settings import get_settings
+from testing.mocks import mock_named_temporary_file
@pytest.fixture
@@ -48,7 +48,7 @@ def test_failure(parser, flag, value):
@staticmethod
def test_precedence_with_only_baseline(parser):
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
f.write(
json.dumps({
'version': '0.0.1',
@@ -69,7 +69,7 @@ def test_precedence_with_only_baseline(parser):
@staticmethod
def test_precedence_with_baseline_and_explicit_value(parser):
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
f.write(
json.dumps({
'version': '0.0.1',
@@ -115,7 +115,7 @@ def test_invalid_classname(parser):
@staticmethod
def test_precedence_with_baseline(parser):
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
f.write(
json.dumps({
'version': '0.0.1',
@@ -148,7 +148,7 @@ def test_success(parser):
# Ensure it serializes accordingly.
parser.parse_args(['-p', 'testing/plugins.py'])
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
baseline.save_to_file(SecretsCollection(), f.name)
f.seek(0)
diff --git a/tests/core/usage/scan_usage_test.py b/tests/core/usage/scan_usage_test.py
index 1933db859..3823ee3db 100644
--- a/tests/core/usage/scan_usage_test.py
+++ b/tests/core/usage/scan_usage_test.py
@@ -1,5 +1,4 @@
import json
-import tempfile
import pytest
@@ -7,6 +6,7 @@
from detect_secrets.core.plugins.util import get_mapping_from_secret_type_to_class
from detect_secrets.core.usage import ParserBuilder
from detect_secrets.settings import get_settings
+from testing.mocks import mock_named_temporary_file
@pytest.fixture
@@ -15,7 +15,7 @@ def parser():
def test_force_use_all_plugins(parser):
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
f.write(
json.dumps({
'version': '0.0.1',
diff --git a/tests/filters/wordlist_filter_test.py b/tests/filters/wordlist_filter_test.py
index a8fa9177f..71073ff43 100644
--- a/tests/filters/wordlist_filter_test.py
+++ b/tests/filters/wordlist_filter_test.py
@@ -1,6 +1,9 @@
+from pathlib import Path
+
import pytest
from detect_secrets import filters
+from detect_secrets.filters.util import compute_file_hash
from detect_secrets.settings import get_settings
from detect_secrets.settings import transient_settings
@@ -17,6 +20,9 @@ def initialize_automaton():
@staticmethod
def test_success():
+ # Compute file_hash manually due to file path operating system differences
+ file_hash = compute_file_hash(Path('test_data/word_list.txt'))
+
# case-insensitivity
assert filters.wordlist.should_exclude_secret('testPass') is True
@@ -25,9 +31,7 @@ def test_success():
assert get_settings().filters['detect_secrets.filters.wordlist.should_exclude_secret'] == {
'min_length': 8,
-
- # Manually computed with `sha1sum test_data/word_list.txt`
- 'file_hash': '116598304e5b33667e651025bcfed6b9a99484c7',
+ 'file_hash': file_hash,
'file_name': 'test_data/word_list.txt',
}
diff --git a/tests/main_test.py b/tests/main_test.py
index 4a2bb98ee..106d0c4bf 100644
--- a/tests/main_test.py
+++ b/tests/main_test.py
@@ -1,17 +1,22 @@
import json
import os
import subprocess
+import sys
import tempfile
from contextlib import contextmanager
from contextlib import redirect_stdout
+from pathlib import Path
from unittest import mock
+import pytest
+
from detect_secrets import main as main_module
from detect_secrets.core import baseline
from detect_secrets.core.secrets_collection import SecretsCollection
from detect_secrets.main import scan_adhoc_string
from detect_secrets.settings import transient_settings
from testing.mocks import disable_gibberish_filter
+from testing.mocks import mock_named_temporary_file
from testing.mocks import mock_printer
@@ -39,7 +44,7 @@ def test_saves_to_baseline():
secrets = SecretsCollection()
old_secrets = baseline.format_for_output(secrets)
- with mock_printer(main_module) as printer, tempfile.NamedTemporaryFile() as f:
+ with mock_printer(main_module) as printer, mock_named_temporary_file() as f:
baseline.save_to_file(old_secrets, f.name)
f.seek(0)
@@ -58,6 +63,10 @@ def test_saves_to_baseline():
assert not printer.message
@staticmethod
+ @pytest.mark.xfail(
+ sys.version_info < (3, 8) and sys.platform == 'win32',
+ reason='TemporaryDirectory correct tear-down requires python 3.8 or higher on windows',
+ )
def test_works_from_different_directory():
with tempfile.TemporaryDirectory() as d:
subprocess.call(['git', '-C', d, 'init'])
@@ -83,26 +92,28 @@ def test_basic():
@staticmethod
def test_restores_line_numbers():
- with tempfile.NamedTemporaryFile('w+') as f:
- with redirect_stdout(f):
- main_module.main(['scan', '--slim', 'test_data/config.env'])
+ with mock_named_temporary_file(mode='w+') as f, redirect_stdout(f):
+ file_a = str(Path('test_data/config.env'))
+ file_b = str(Path('test_data/config.md'))
+
+ main_module.main(['scan', '--slim', file_a])
f.seek(0)
main_module.main([
'scan',
- '--slim', 'test_data/config.md', 'test_data/config.env',
+ '--slim', file_a, file_b,
'--baseline', f.name,
])
f.seek(0)
secrets = baseline.load(baseline.load_from_file(f.name))
- # Make sure both old and new files exist
- assert secrets.files == {'test_data/config.env', 'test_data/config.md'}
+ # Make sure both old and new files exist
+ assert secrets.files == {file_a, file_b}
- # Make sure they both have line numbers
- assert list(secrets['test_data/config.env'])[0].line_number
- assert list(secrets['test_data/config.md'])[0].line_number
+ # Make sure they both have line numbers
+ assert list(secrets[file_a])[0].line_number
+ assert list(secrets[file_b])[0].line_number
class TestScanString:
@@ -183,7 +194,7 @@ def test_basic(mock_log):
for item in output['filters_used']
}
- with tempfile.NamedTemporaryFile() as f, mock.patch(
+ with mock_named_temporary_file() as f, mock.patch(
'detect_secrets.audit.io.get_user_decision',
return_value='s',
):
diff --git a/tests/plugins/keyword_test.py b/tests/plugins/keyword_test.py
index ce8b0748c..c9591db51 100644
--- a/tests/plugins/keyword_test.py
+++ b/tests/plugins/keyword_test.py
@@ -13,7 +13,7 @@
LETTER_SECRET = 'A,.:-¨@*¿?!'
SYMBOL_SECRET = ',.:-¨@*¿?!'
-LONG_LINE = ''.format(base64.b64encode((str(randint(0, 9)) * 30500).encode())) # noqa: E501
+LONG_LINE = ''.format(base64.b64encode((str(randint(0, 9)) * 24000).encode())) # noqa: E501
CONFIG_TEST_CASES = [
('password = "{}"'.format(WHITES_SECRET), WHITES_SECRET),
diff --git a/tests/plugins/private_key_test.py b/tests/plugins/private_key_test.py
index 533ead347..b4a40a39c 100644
--- a/tests/plugins/private_key_test.py
+++ b/tests/plugins/private_key_test.py
@@ -1,9 +1,8 @@
-import tempfile
-
import pytest
from detect_secrets.core.secrets_collection import SecretsCollection
from detect_secrets.settings import transient_settings
+from testing.mocks import mock_named_temporary_file
@pytest.mark.parametrize(
@@ -22,7 +21,7 @@
],
)
def test_basic(file_content):
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
f.write(file_content.encode())
f.seek(0)
diff --git a/tests/pre_commit_hook_test.py b/tests/pre_commit_hook_test.py
index 4c44fd7fe..1fba49131 100644
--- a/tests/pre_commit_hook_test.py
+++ b/tests/pre_commit_hook_test.py
@@ -1,5 +1,4 @@
import json
-import tempfile
from contextlib import contextmanager
from functools import partial
from typing import List
@@ -12,6 +11,7 @@
from detect_secrets.pre_commit_hook import main
from detect_secrets.settings import transient_settings
from testing.mocks import disable_gibberish_filter
+from testing.mocks import mock_named_temporary_file
@pytest.fixture(autouse=True)
@@ -54,7 +54,7 @@ def test_baseline_filters_out_known_secrets():
assert secrets
with disable_gibberish_filter():
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
baseline.save_to_file(secrets, f.name)
f.seek(0)
@@ -68,7 +68,7 @@ def test_baseline_filters_out_known_secrets():
# Remove one arbitrary secret, so that it won't be the full set.
secrets.data['test_data/each_secret.py'].pop()
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
baseline.save_to_file(secrets, f.name)
f.seek(0)
@@ -128,7 +128,7 @@ def get_baseline_file(self, formatter=baseline.format_for_output):
secrets = SecretsCollection()
secrets.scan_file(self.FILENAME)
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
with mock.patch('detect_secrets.core.baseline.VERSION', '0.0.1'):
data = formatter(secrets)
@@ -143,7 +143,7 @@ class TestLineNumberChanges:
FILENAME = 'test_data/files/file_with_secrets.py'
def test_modifies_baseline(self, modified_baseline):
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
baseline.save_to_file(modified_baseline, f.name)
assert_commit_blocked_with_diff_exit_code([
@@ -153,7 +153,7 @@ def test_modifies_baseline(self, modified_baseline):
])
def test_does_not_modify_slim_baseline(self, modified_baseline):
- with tempfile.NamedTemporaryFile() as f:
+ with mock_named_temporary_file() as f:
baseline.save_to_file(
baseline.format_for_output(modified_baseline, is_slim_mode=True),
f.name,