def test_have_sources(self): action = action_tracer.Action(script="script.sh", sources=["input.src", "main.h"]) self.assertEqual( action.access_constraints(), action_tracer.AccessConstraints( allowed_reads=abspaths({"script.sh", "input.src", "main.h"})))
def test_links_are_followed(self): def fake_realpath(s: str) -> str: return f'test/realpath/{s}' action = action_tracer.Action(inputs=["script.sh"], depfile="foo.d") with mock.patch.object(os.path, 'exists', return_value=True) as mock_exists: with mock.patch("builtins.open", mock.mock_open( read_data="foo.o: foo.cc foo.h\n")) as mock_file: with mock.patch.object(os.path, 'realpath', wraps=fake_realpath) as mock_realpath: constraints = action.access_constraints( writeable_depfile_inputs=False) mock_exists.assert_called_once() mock_file.assert_called_once() mock_realpath.assert_called() self.assertEqual( constraints, action_tracer.AccessConstraints( allowed_reads=abspaths( { "test/realpath/script.sh", "test/realpath/foo.d", "test/realpath/foo.o", "test/realpath/foo.cc", "test/realpath/foo.h", }), allowed_writes=abspaths({"foo.d", "foo.o"})))
def test_fresh_output_with_used_input(self): def fake_getctime(path: str): if path.startswith("read"): return 100 if path.startswith("write"): return 200 return 0 used_input = "read.me" written_output = "write.me" with mock.patch.object(os.path, 'exists', return_value=True) as mock_exists: with mock.patch.object(os.path, 'getctime', wraps=fake_getctime) as mock_ctime: output_diagnostics = action_tracer.diagnose_stale_outputs( accesses=[ action_tracer.Read(used_input), action_tracer.Write(written_output), ], access_constraints=action_tracer.AccessConstraints( allowed_reads={used_input}, required_writes={written_output}), ) # There are no untouched outputs, so getctime is never called. mock_exists.assert_not_called() mock_ctime.assert_not_called() self.assertEqual( output_diagnostics, action_tracer.StalenessDiagnostics( required_writes={written_output}, # newest_input is not evaluated stale_outputs=set(), ), )
def test_have_response_file(self): action = action_tracer.Action(script="script.sh", response_file_name="response.out") self.assertEqual( action.access_constraints(), action_tracer.AccessConstraints( allowed_reads=abspaths({"script.sh", "response.out"})))
def test_stale_output_with_used_input(self): def fake_read_ctime(path: str): if path.startswith("read"): return 200 raise ValueError(f'Unexpected path: {path}') def fake_write_ctime(path: str): if path.startswith("write"): return 100 raise ValueError(f'Unexpected path: {path}') used_input = "read.me" required_writes = {"write.me"} with mock.patch.object(os.path, 'exists', return_value=True) as mock_exists: with mock.patch.object(os.path, 'getctime', wraps=fake_read_ctime) as mock_read_ctime: with mock.patch.object( action_tracer, 'realpath_ctime', wraps=fake_write_ctime) as mock_write_ctime: output_diagnostics = action_tracer.diagnose_stale_outputs( accesses=[action_tracer.Read(used_input)], access_constraints=action_tracer.AccessConstraints( allowed_reads={used_input}, required_writes=required_writes), ) mock_exists.assert_called_once() mock_read_ctime.assert_called() mock_write_ctime.assert_called() self.assertEqual( output_diagnostics, action_tracer.StalenessDiagnostics(required_writes=required_writes, newest_input=used_input, stale_outputs={"write.me"}), )
def test_no_accesses(self): self.assertEqual( action_tracer.check_access_permissions( action_tracer.FSAccessSet(), action_tracer.AccessConstraints()), action_tracer.FSAccessSet(), )
def test_have_inputs(self): action = action_tracer.Action( inputs=["script.sh", "input.txt", "main.cc"]) self.assertEqual( action.access_constraints(), action_tracer.AccessConstraints( allowed_reads=abspaths({"script.sh", "input.txt", "main.cc"})))
def test_ok_write(self): self.assertEqual( action_tracer.check_access_permissions( action_tracer.FSAccessSet(writes={"writeable.txt"}), action_tracer.AccessConstraints( allowed_writes={"writeable.txt"})), action_tracer.FSAccessSet(), )
def test_forbidden_read(self): read = "unreadable.txt" self.assertEqual( action_tracer.check_access_permissions( action_tracer.FSAccessSet(reads={read}), action_tracer.AccessConstraints()), action_tracer.FSAccessSet(reads={read}), )
def test_ok_read(self): self.assertEqual( action_tracer.check_access_permissions( action_tracer.FSAccessSet(reads={"readable.txt"}), action_tracer.AccessConstraints( allowed_reads={"readable.txt"})), action_tracer.FSAccessSet(), )
def test_have_outputs(self): action = action_tracer.Action(script="script.sh", outputs=["main.o"]) self.assertEqual( action.access_constraints(), action_tracer.AccessConstraints( allowed_reads=abspaths({"script.sh", "main.o"}), allowed_writes=abspaths({"main.o"}), required_writes=abspaths({"main.o"})))
def test_no_accesses_no_constraints(self): output_diagnostics = action_tracer.diagnose_stale_outputs( accesses=[], access_constraints=action_tracer.AccessConstraints(), ) self.assertEqual( output_diagnostics, action_tracer.StalenessDiagnostics(), )
def test_have_nonexistent_depfile(self): action = action_tracer.Action(depfile="foo.d") with mock.patch.object(os.path, 'exists', return_value=False) as mock_exists: constraints = action.access_constraints() mock_exists.assert_called() self.assertEqual( constraints, action_tracer.AccessConstraints(allowed_writes=abspaths({"foo.d"}), allowed_reads=abspaths({"foo.d"})))
def test_forbidden_writes(self): # make sure multiple violations accumulate bad_writes = { "unwriteable.txt", "you-shall-not-pass.txt", } self.assertEqual( action_tracer.check_access_permissions( action_tracer.FSAccessSet(writes=bad_writes), action_tracer.AccessConstraints()), action_tracer.FSAccessSet(writes=bad_writes), )
def test_no_inputs(self): access_constraints = action_tracer.AccessConstraints( allowed_reads={"input"}, required_writes={"output"}) with mock.patch.object(os.path, 'exists', return_value=False) as mock_exists: with mock.patch.object(os.path, 'getctime', return_value=0) as mock_ctime: fresh_outputs = access_constraints.fresh_outputs() mock_exists.assert_called() mock_ctime.assert_not_called() self.assertEqual(fresh_outputs, set())
def test_missing_write_no_inputs(self): required_writes = {"write.me"} output_diagnostics = action_tracer.diagnose_stale_outputs( accesses=[], access_constraints=action_tracer.AccessConstraints( required_writes=required_writes), ) self.assertEqual( output_diagnostics, action_tracer.StalenessDiagnostics( required_writes=required_writes, nonexistent_outputs={"write.me"}), )
def test_read_from_temporary_writes_ok(self): temp_file = "__file.tmp" reads = {temp_file} writes = { "unwriteable.txt", temp_file, } self.assertEqual( action_tracer.check_access_permissions( action_tracer.FSAccessSet(reads=reads, writes=writes), action_tracer.AccessConstraints()), action_tracer.FSAccessSet(reads=set(), writes=writes), )
def test_stale_output_no_inputs(self): required_writes = {"write.me"} with mock.patch.object(os.path, 'exists', return_value=True) as mock_exists: output_diagnostics = action_tracer.diagnose_stale_outputs( accesses=[], access_constraints=action_tracer.AccessConstraints( required_writes=required_writes), ) mock_exists.assert_called_once() self.assertEqual( output_diagnostics, action_tracer.StalenessDiagnostics(required_writes=required_writes), )
def test_missing_write_with_used_input(self): used_input = "read.me" required_writes = {"write.me"} output_diagnostics = action_tracer.diagnose_stale_outputs( accesses=[action_tracer.Read(used_input)], access_constraints=action_tracer.AccessConstraints( allowed_reads={used_input}, required_writes=required_writes, ), ) self.assertEqual( output_diagnostics, action_tracer.StalenessDiagnostics( required_writes=required_writes, nonexistent_outputs={"write.me"}), )
def test_have_depfile(self): action = action_tracer.Action(script="script.sh", depfile="foo.d") with mock.patch.object(os.path, 'exists', return_value=True) as mock_exists: with mock.patch( "builtins.open", mock.mock_open( read_data="foo.o: foo.cc foo.h\n")) as mock_file: constraints = action.access_constraints() self.assertEqual( constraints, action_tracer.AccessConstraints( allowed_reads=abspaths( {"script.sh", "foo.d", "foo.o", "foo.cc", "foo.h"}), allowed_writes=abspaths({"foo.d", "foo.o", "foo.cc", "foo.h"})))
def test_stale_output_with_multiple_used_inputs(self): def fake_read_ctime(path: str): if path == "read.me": return 200 if path == "read.me.newer": return 300 raise Exception(f'fake_read_ctime for unexpected path: {path}') def fake_write_ctime(path: str): if path.startswith("write"): return 250 raise Exception(f'fake_write_ctime for unexpected path: {path}') used_input = "read.me" # Make sure the timestamp of the newest input is used for comparison. used_input_newer = "read.me.newer" required_writes = {"write.me"} with mock.patch.object(os.path, 'exists', return_value=True) as mock_exists: with mock.patch.object(os.path, 'getctime', wraps=fake_read_ctime) as mock_read_ctime: with mock.patch.object( action_tracer, 'realpath_ctime', wraps=fake_write_ctime) as mock_write_ctime: output_diagnostics = action_tracer.diagnose_stale_outputs( accesses=[ action_tracer.Read(used_input), action_tracer.Read(used_input_newer), ], access_constraints=action_tracer.AccessConstraints( allowed_reads={used_input, used_input_newer}, required_writes=required_writes), ) mock_exists.assert_called_once() mock_read_ctime.assert_called() mock_write_ctime.assert_called() self.assertEqual( output_diagnostics, action_tracer.StalenessDiagnostics( required_writes=required_writes, # newer input is used for comparison newest_input=used_input_newer, stale_outputs={"write.me"}), )
def test_readable_output_is_fresh(self): def fake_getctime(path: str): if path == "input": return 200 if path == "output": return 300 raise ValueError(f'Unexpected path: {path}') access_constraints = action_tracer.AccessConstraints( allowed_reads={"input", "output"}, required_writes={"output"}) with mock.patch.object(os.path, 'exists', return_value=True) as mock_exists: with mock.patch.object(action_tracer, 'realpath_ctime', wraps=fake_getctime) as mock_ctime: fresh_outputs = access_constraints.fresh_outputs() mock_exists.assert_called() mock_ctime.assert_called() self.assertEqual(fresh_outputs, {"output"})
def test_empty_action(self): action = action_tracer.Action(script="script.sh") self.assertEqual( action.access_constraints(), action_tracer.AccessConstraints( allowed_reads=abspaths({"script.sh"})))