mariadb-haskell-0.1.0.0: validation/test_check.py
"""A broken acceptance reference must not leave an older run looking current."""
import json
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
import check
class CheckFailureTests(unittest.TestCase):
def test_build_launch_failure_is_recorded(self):
self.check_process_failure("build", FileNotFoundError("cabal missing"), "build")
def test_recorder_launch_failure_is_recorded(self):
self.check_process_failure("recorder", OSError("cannot start recorder"), "suite")
def test_unreadable_recorder_result_is_recorded(self):
self.check_process_failure("json", None, "suite")
def test_interruption_is_recorded(self):
self.check_process_failure("build", KeyboardInterrupt(), "build", expected_status=130)
def check_process_failure(self, failure_at, error, stage, *, expected_status=1):
with tempfile.TemporaryDirectory() as folder:
root = Path(folder)
output = root / "run"
config = root / "config.json"
config.write_text("{}")
inputs = {"sha256": "candidate", "files": {}, "commit": "parent"}
def recorder(*args, **kwargs):
if failure_at == "recorder":
raise error
output.mkdir()
(output / "results.json").write_text("{")
return 0
with (patch("sys.argv", ["check.py", "--working-tree", "--output", str(output),
"--config", str(config)]),
patch.object(check.live, "input_snapshot", return_value=inputs),
patch.object(check.run, "sql_client", return_value="12.3.3-MariaDB"),
patch.object(check.subprocess, "run") as build,
patch.object(check.subprocess, "call", side_effect=recorder),
patch.object(check, "install_runtime_data", return_value=0) as install,
patch.object(check.live, "publish") as publish):
if failure_at == "install":
install.side_effect = error
if failure_at == "build":
build.side_effect = error
else:
build.return_value.returncode = 0
self.assertEqual(expected_status, check.main())
attempt, data = publish.call_args.args
self.assertEqual("FAILED", attempt["state"])
self.assertEqual(stage, attempt["stage"])
self.assertIsNone(data)
def test_runtime_data_installation_failure_is_recorded(self):
self.check_process_failure("install", OSError("copy failed"), "runtime data installation")
def test_runtime_data_installation_interruption_is_recorded(self):
self.check_process_failure("install", KeyboardInterrupt(), "runtime data installation", expected_status=130)
def test_install_selects_library_not_executable_setup(self):
with tempfile.TemporaryDirectory() as folder:
package = Path(folder)/"mysql-haskell-1.3.3"
library = package/"opt"
component = package/"x/mariadb-validation/opt"
for build in [library, component]:
build.mkdir(parents=True)
(build/"setup-config").touch()
module = library/"build/Database/MySQL/Connection.hi"
module.parent.mkdir(parents=True)
module.touch()
binary = component/"build/mariadb-validation/mariadb-validation"
with (patch.object(check.run, "executable", return_value=str(binary)),
patch.object(check.subprocess, "run") as copy):
copy.return_value.returncode=0
self.assertEqual(0, check.install_runtime_data())
self.assertIn("--builddir="+str(library), copy.call_args.args[0])
def test_missing_library_install_configuration_is_rejected(self):
with patch.object(check.run, "executable", return_value="/no/library/executable"):
with self.assertRaisesRegex(RuntimeError, "Setup configuration"):
check.install_runtime_data()
def test_bad_reference_publishes_failed_attempt_and_retains_raw_candidate(self):
self.check_reference_failure(ValueError("hash mismatch"))
def test_absent_manifest_cannot_disable_the_ratchet(self):
self.check_reference_failure(FileNotFoundError("accepted.json absent"), missing=True)
def check_reference_failure(self, error, *, missing=False):
original_exists = Path.exists
def exists(path):
return False if missing and path.name == "accepted.json" else original_exists(path)
with tempfile.TemporaryDirectory() as folder:
root = Path(folder)
output = root / "run"
config = root / "config.json"
config.write_text("{}")
inputs = {"sha256": "candidate", "files": {}, "commit": "parent"}
def recorder(*args, **kwargs):
output.mkdir()
(output / "results.json").write_text(json.dumps({"complete_suite": True}))
return 0
with (patch("sys.argv", ["check.py", "--working-tree", "--output", str(output),
"--config", str(config)]),
patch.object(check.live, "input_snapshot", return_value=inputs),
patch.object(check.run, "sql_client", return_value="12.3.3-MariaDB"),
patch.object(check.subprocess, "run") as build,
patch.object(check.subprocess, "call", side_effect=recorder),
patch.object(check.stage1_gates, "reference", side_effect=error) as reference,
patch.object(Path, "exists", exists),
patch.object(check, "install_runtime_data", return_value=0) as install,
patch.object(check.live, "publish") as publish):
build.return_value.returncode = 0
self.assertEqual(1, check.main())
reference.assert_called_once()
attempt, data = publish.call_args.args
self.assertEqual("FAILED", attempt["state"])
self.assertEqual("acceptance evidence", attempt["stage"])
self.assertIn(str(error), attempt["reason"])
self.assertIsNone(data)
self.assertTrue((output / "results.json").exists())
class NativeWrapperTests(unittest.TestCase):
def test_native_failure_is_published(self):
self.check_native(RuntimeError('native failed'), 1)
def test_native_interruption_is_published(self):
self.check_native(KeyboardInterrupt(), 130)
def test_native_success_is_attached_to_completed_run(self):
self.check_native(None, 0)
def test_native_original_alarm_retains_complete_results(self):
self.check_native(None, 1, native_timing=[{'id': 'bulk.native', 'percent': 21}])
def test_native_investigation_is_not_automatically_accepted(self):
self.check_native(None, 0, native_timing=[{'id': 'bulk.native', 'percent': 6}])
def check_native(self, failure, status, native_timing=None):
with tempfile.TemporaryDirectory() as folder:
root = Path(folder)
(root / 'results').mkdir()
output = root / 'run'
config = Path(folder) / 'config.json'
config.write_text('{}')
inputs = {'sha256': 'candidate', 'files': {}, 'commit': 'parent'}
candidate = {'complete_suite': True}
gate = {'correctness_accepted': True, 'evidence_accepted': True}
native_data = {'complete': True, 'inputs': inputs}
if native_timing:
native_data['baseline_comparison'] = {'timing_investigations': native_timing}
def recorder(*args, **kwargs):
output.mkdir()
(output / 'results.json').write_text(json.dumps(candidate))
return 0
with (patch('sys.argv', ['check.py', '--working-tree', '--config', str(config), '--output', str(output)]),
patch.object(check.live, 'input_snapshot', return_value=inputs),
patch.object(check.run, 'HERE', root),
patch.object(check.run, 'sql_client', return_value='12.3.3-MariaDB'),
patch.object(check.subprocess, 'run') as build,
patch.object(check.subprocess, 'call', side_effect=recorder),
patch.object(check, 'install_runtime_data', return_value=0),
patch.object(check.stage1_gates, 'reference', return_value=({}, {})),
patch.object(check.stage1_gates, 'compare', return_value=gate),
patch.object(check, 'native_benchmarks', return_value=native_data, side_effect=failure) as native,
patch('stage3_benchmarks.verify_evidence'),
patch.object(check.live, 'publish') as publish):
build.return_value.returncode = 0
self.assertEqual(status, check.main())
native.assert_called_once_with(output.resolve(), 7, config)
attempt, data = publish.call_args.args
if failure is None:
self.assertEqual('COMPLETED', attempt['state'])
self.assertEqual(native_data, attempt['native_features'])
self.assertEqual(candidate, data)
if native_timing:
self.assertTrue(gate['performance_requires_confirmation'])
else:
self.assertEqual('FAILED', attempt['state'])
self.assertEqual('native benchmarks', attempt['stage'])
self.assertIsNone(data)
self.assertTrue((output / 'results.json').exists())
class SessionReuseWrapperTests(unittest.TestCase):
def test_success_is_attached(self):
self.check_reuse(None, 0)
def test_failure_retains_raw_candidate(self):
self.check_reuse(RuntimeError('reuse failed'), 1)
def test_interruption_is_recorded(self):
self.check_reuse(KeyboardInterrupt(), 130)
def test_large_timing_alarm_keeps_complete_measurements(self):
self.check_reuse(None, 1, timing=[{'id': 'reuse.tcp.reset', 'percent': 21}])
def test_strict_investigation_remains_open(self):
self.check_reuse(None, 0, timing=[{'id': 'reuse.tcp.reset', 'percent': 6}])
def check_reuse(self, failure, status, timing=None):
with tempfile.TemporaryDirectory() as folder:
root = Path(folder)
(root / 'results').mkdir()
output = root / 'run'
config = root / 'config.json'
config.write_text('{}')
inputs = {'sha256': 'candidate', 'files': {}, 'commit': 'parent'}
candidate = {'complete_suite': True, 'correctness': [{'id': 'stage4.fixture', 'status': 'PASS'}]}
gate = {'correctness_accepted': True, 'evidence_accepted': True}
reuse_data = {'complete': True, 'inputs': inputs,
'baseline_comparison': {'timing_investigations': timing or [], 'allocation_investigations': []}}
def recorder(*args, **kwargs):
output.mkdir()
(output / 'results.json').write_text(json.dumps(candidate))
return 0
with (patch('sys.argv', ['check.py', '--working-tree', '--config', str(config), '--output', str(output)]),
patch.object(check.live, 'input_snapshot', return_value=inputs),
patch.object(check.run, 'HERE', root),
patch.object(check.run, 'sql_client', return_value='12.3.3-MariaDB'),
patch.object(check.subprocess, 'run') as build,
patch.object(check.subprocess, 'call', side_effect=recorder),
patch.object(check, 'install_runtime_data', return_value=0),
patch.object(check.stage1_gates, 'reference', return_value=({}, {})),
patch.object(check.stage1_gates, 'compare', return_value=gate),
patch.object(check, 'native_benchmarks', return_value={'complete': True}),
patch('stage3_benchmarks.verify_evidence'),
patch('stage4_benchmarks.verify_evidence'),
patch.object(check, 'session_reuse_benchmarks', return_value=reuse_data, side_effect=failure) as reuse_call,
patch.object(check.live, 'publish') as publish):
build.return_value.returncode = 0
self.assertEqual(status, check.main())
reuse_call.assert_called_once_with(output.resolve(), 7, config)
attempt, data = publish.call_args.args
if failure is None:
self.assertEqual('COMPLETED', attempt['state'])
self.assertEqual(candidate, data)
self.assertEqual(reuse_data, attempt['session_reuse'])
if timing:
self.assertTrue(gate['performance_requires_confirmation'])
else:
self.assertEqual('FAILED', attempt['state'])
self.assertEqual('session reuse benchmarks', attempt['stage'])
self.assertIsNone(data)
self.assertTrue((output / 'results.json').is_file())
if __name__ == "__main__":
unittest.main()