138 lines
5.7 KiB
Python
138 lines
5.7 KiB
Python
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
SCRIPT = Path(__file__).parents[2] / "scripts/check-config-boundaries.py"
|
|
|
|
|
|
class ConfigBoundaryTests(unittest.TestCase):
|
|
def run_check(self, root: Path, *files: str) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
["python3", str(SCRIPT), "--root", str(root), "--files", *files],
|
|
text=True,
|
|
capture_output=True,
|
|
check=False,
|
|
)
|
|
|
|
def test_value_only_production_code_passes(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
path = root / "crates/example/src/lib.rs"
|
|
path.parent.mkdir(parents=True)
|
|
path.write_text("pub fn configured(value: u32) -> u32 { value }\n", encoding="utf-8")
|
|
result = self.run_check(root, "crates/example/src/lib.rs")
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
|
|
def test_direct_alias_and_hidden_reader_forms_fail(self) -> None:
|
|
cases = (
|
|
"std::env::var(\"X\")",
|
|
"std /* split */ ::\n env :: var(\"X\")",
|
|
"use std::env as process_environment;",
|
|
"use std::{collections::BTreeMap, env};",
|
|
"pub use std :: env :: var as exported_reader;",
|
|
"use std as standard; standard :: env :: var(\"X\");",
|
|
"extern crate std as standard; standard::env::vars();",
|
|
"use std::{self as system}; system::env::var(\"X\");",
|
|
"env::vars_os()",
|
|
"fn community_from_env() {}",
|
|
"dotenvy::dotenv()",
|
|
"option_env!(\"SECRET\")",
|
|
"env!(\"SECRET\")",
|
|
)
|
|
for index, source in enumerate(cases):
|
|
with self.subTest(source=source), tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
relative = f"crates/example/src/case_{index}.rs"
|
|
path = root / relative
|
|
path.parent.mkdir(parents=True)
|
|
path.write_text(source, encoding="utf-8")
|
|
result = self.run_check(root, relative)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
|
|
def test_only_package_version_compile_time_macro_is_allowed(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
relative = "apps/example/src/main.rs"
|
|
path = root / relative
|
|
path.parent.mkdir(parents=True)
|
|
path.write_text(
|
|
'const VERSION: &str = env! ( "CARGO_PKG_VERSION" );\n',
|
|
encoding="utf-8",
|
|
)
|
|
result = self.run_check(root, relative)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
|
|
def test_command_line_arguments_are_not_environment_configuration(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
relative = "apps/example/src/main.rs"
|
|
path = root / relative
|
|
path.parent.mkdir(parents=True)
|
|
path.write_text(
|
|
"fn main() { let _ = std::env::args().next(); }\n",
|
|
encoding="utf-8",
|
|
)
|
|
result = self.run_check(root, relative)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
|
|
def test_comments_and_strings_do_not_trigger_false_positives(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
relative = "crates/example/src/lib.rs"
|
|
path = root / relative
|
|
path.parent.mkdir(parents=True)
|
|
path.write_text(
|
|
'// std::env::var("X")\nconst NOTE: &str = "option_env!(\\\"X\\\")";\n',
|
|
encoding="utf-8",
|
|
)
|
|
result = self.run_check(root, relative)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
|
|
def test_missing_traversal_and_symlink_paths_fail_closed(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
outside = root.parent / "config-boundary-outside.rs"
|
|
outside.write_text("std::env::var(\"X\");", encoding="utf-8")
|
|
self.addCleanup(outside.unlink, missing_ok=True)
|
|
for supplied in ("missing.rs", "../config-boundary-outside.rs"):
|
|
result = self.run_check(root, supplied)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
link = root / "link.rs"
|
|
link.symlink_to(outside)
|
|
result = self.run_check(root, "link.rs")
|
|
self.assertNotEqual(result.returncode, 0)
|
|
|
|
def test_default_scan_covers_build_scripts_and_rejects_symlinks(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
build = root / "crates/example/build.rs"
|
|
build.parent.mkdir(parents=True)
|
|
build.write_text('std::env::var("X");', encoding="utf-8")
|
|
result = subprocess.run(
|
|
["python3", str(SCRIPT), "--root", str(root)],
|
|
text=True,
|
|
capture_output=True,
|
|
check=False,
|
|
)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
|
|
build.unlink()
|
|
outside = root / "outside.rs"
|
|
outside.write_text("fn safe() {}", encoding="utf-8")
|
|
link = root / "apps/example/src/link.rs"
|
|
link.parent.mkdir(parents=True)
|
|
link.symlink_to(outside)
|
|
result = subprocess.run(
|
|
["python3", str(SCRIPT), "--root", str(root)],
|
|
text=True,
|
|
capture_output=True,
|
|
check=False,
|
|
)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|