141 lines
4.8 KiB
Python
141 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Behavior tests for probability_map.py."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import csv
|
|
import io
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from contextlib import redirect_stderr
|
|
from pathlib import Path
|
|
|
|
import generate_input
|
|
import probability_map
|
|
|
|
|
|
class PrepareHashTests(unittest.TestCase):
|
|
def test_compiles_c_source_when_binary_is_missing(self) -> None:
|
|
with tempfile.TemporaryDirectory() as temporary_directory:
|
|
root = Path(temporary_directory)
|
|
hash_directory = root / "constant"
|
|
hash_directory.mkdir()
|
|
(hash_directory / "bin_hash.c").write_text(
|
|
"#include <stdio.h>\n"
|
|
"int main(void) { puts(\"00000001\"); return 0; }\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
executable = probability_map.prepare_hash("constant", root)
|
|
|
|
self.assertEqual(executable, hash_directory / "bin_hash")
|
|
self.assertEqual(
|
|
subprocess.check_output([executable, "word"], text=True).strip(),
|
|
"00000001",
|
|
)
|
|
|
|
def test_adds_python_shebang_and_execute_permission(self) -> None:
|
|
with tempfile.TemporaryDirectory() as temporary_directory:
|
|
root = Path(temporary_directory)
|
|
hash_directory = root / "python_hash"
|
|
hash_directory.mkdir()
|
|
source = hash_directory / "bin_hash.py"
|
|
source.write_text("print('00000002')\n", encoding="utf-8")
|
|
|
|
executable = probability_map.prepare_hash("python_hash", root)
|
|
|
|
self.assertEqual(executable, source)
|
|
self.assertTrue(os.access(source, os.X_OK))
|
|
self.assertTrue(
|
|
source.read_text(encoding="utf-8").startswith(
|
|
"#!/usr/bin/env python3\n"
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
subprocess.check_output([executable, "word"], text=True).strip(),
|
|
"00000002",
|
|
)
|
|
|
|
|
|
class ProbabilityTests(unittest.TestCase):
|
|
def test_counts_changed_hash_bits_relative_to_source(self) -> None:
|
|
probabilities = probability_map.bit_probabilities(
|
|
source_hash=0b0000,
|
|
changed_hashes=[0b0001, 0b0011, 0b0010, 0b0000],
|
|
bits=4,
|
|
)
|
|
|
|
self.assertEqual(probabilities, [0.5, 0.5, 0.0, 0.0])
|
|
|
|
def test_csv_table_has_operation_rows_and_bit_columns(self) -> None:
|
|
stream = io.StringIO()
|
|
|
|
probability_map.write_csv_table(
|
|
stream,
|
|
{
|
|
"replace": (4, [0.25, 0.75]),
|
|
"delete": (2, [0.5, 0.0]),
|
|
},
|
|
)
|
|
|
|
rows = list(csv.reader(io.StringIO(stream.getvalue())))
|
|
self.assertEqual(rows[0], ["operation", "pairs", "bit_0", "bit_1"])
|
|
self.assertEqual(rows[1], ["replace", "4", "0.250000", "0.750000"])
|
|
self.assertEqual(rows[2], ["delete", "2", "0.500000", "0.000000"])
|
|
|
|
def test_parser_accepts_multiple_source_words(self) -> None:
|
|
arguments = probability_map.build_parser().parse_args(["first", "second"])
|
|
|
|
self.assertEqual(arguments.words, ["first", "second"])
|
|
|
|
def test_aggregates_samples_from_multiple_words_and_warns_on_shortfall(self) -> None:
|
|
with tempfile.TemporaryDirectory() as temporary_directory:
|
|
executable = Path(temporary_directory) / "hash.py"
|
|
executable.write_text(
|
|
"#!/usr/bin/env python3\n"
|
|
"import sys\n"
|
|
"print(f'{sum(sys.argv[1].encode()):08x}')\n",
|
|
encoding="utf-8",
|
|
)
|
|
executable.chmod(0o755)
|
|
warnings = io.StringIO()
|
|
|
|
with redirect_stderr(warnings):
|
|
table = probability_map.build_probability_table(
|
|
executable=executable,
|
|
sources=["A", "B"],
|
|
operations=["delete"],
|
|
count=10,
|
|
operation_count=1,
|
|
alphabet=generate_input.DEFAULT_ALPHABET,
|
|
seed=1,
|
|
max_attempts=None,
|
|
)
|
|
|
|
pair_count, probabilities = table["delete"]
|
|
self.assertEqual(pair_count, 2)
|
|
self.assertEqual(
|
|
probabilities[:8],
|
|
[0.5, 0.5, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
|
|
)
|
|
self.assertEqual(warnings.getvalue().count("delete"), 2)
|
|
|
|
|
|
class GenerateWordsTests(unittest.TestCase):
|
|
def test_count_is_an_upper_bound_when_unique_results_are_exhausted(self) -> None:
|
|
words = generate_input.generate_words(
|
|
source="abc",
|
|
count=100,
|
|
operation="delete",
|
|
operation_count=1,
|
|
seed=42,
|
|
)
|
|
|
|
self.assertEqual(set(words), {"ab", "ac", "bc"})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|