def test_variables(): """Check that an action's variables are properly substituted.""" variable = feature('variable', attributes=incidental) class A(action): var = map(variable, join) command = 'echo $(var)' a = artefact('a', attrs=notfile | always) b = artefact('b', attrs=notfile | always) c = artefact('c', attrs=notfile | always) echo = action('echo', 'echo $(variable)') pye = action('pyecho', pyecho, ['variable']) a = rule(A(), a, features=variable('A')) b = rule(echo, b, a, features=variable('B')) c = rule(pye, c, b, features=variable('C')) with patch('faber.action.action.__status__') as recipe: assert a.update() (_, _, _, _, output, _), kwds = recipe.call_args_list[-1] assert output.strip() == 'A' assert b.update() (_, _, _, _, output, _), kwds = recipe.call_args_list[-1] assert output.strip() == 'B' assert c.update() (_, _, _, _, output, _), kwds = recipe.call_args_list[-1] assert output.strip() == "c <- b (variable=['C'])"
def test_implicit_rule(): a1 = action('a1', 'something') a2 = action('a2', 'something') a3 = action('a3', 'something') c_ = artefact('c', type=c) cc = artefact('cc', type=cxx) o = artefact('o', type=obj) l = artefact('l', type=lib) assembly.implicit_rule(a1, obj, c) assembly.implicit_rule(a2, obj, cxx) assembly.implicit_rule(a3, lib, obj) with patch('faber.assembly.explicit_rule') as rule: assembly.rule(o, c_) assembly.rule(o, cc) assert rule.call_count == 2 assert (rule.call_args_list[0][0][0] is a1 and rule.call_args_list[0][0][1] is o) assert (rule.call_args_list[1][0][0] is a2 and rule.call_args_list[1][0][1] is o) with patch('faber.assembly.explicit_rule') as rule: assembly.rule(l, cc) assert rule.call_count == 2 assert (rule.call_args_list[0][0][0] is a2 and rule.call_args_list[0][1]['attrs'] & intermediate) assert (rule.call_args_list[1][0][0] is a3 and rule.call_args_list[1][0][1] is l)
def test_composite(): """Test the workflow of faber.config.try_compile""" src = rule(fileutils.touch, 'src', attrs=intermediate | always) obj = artefact('obj', attrs=intermediate) check = artefact('check', attrs=notfile) def assemble(targets, sources): rule(fileutils.copy, obj, src, attrs=intermediate, module=targets[0].module) # make a dependency graph ass = rule(assemble, 'ass', attrs=notfile | always) # make a binary depend(obj, dependencies=ass) # and test it check = alias(check, obj) with patch('faber.action.action.__status__') as recipe: scheduler.update([check]) (_, status, _, _, _, _), kwds = recipe.call_args_list[-1] assert recipe.call_count == 3 assert status is True
def test_noop(): """Check that an artefact won't be updated if a dependent artefact is up to date.""" a = artefact('a', attrs=notfile) b = artefact('b', attrs=notfile) b = rule(pyecho, b, a) with patch('faber.scheduler._report_recipe') as recipe: assert scheduler.update(b) assert not recipe.called
def test_noop(): """Check that an artefact won't be updated if a dependent artefact is up to date.""" a = artefact('a', attrs=notfile) b = artefact('b', attrs=notfile) b = rule(pyecho, b, a) with patch('faber.action.action.__status__') as recipe: assert b.update() assert not recipe.called
def test_late(): """Test that a "late" dependency raises an error.""" a = artefact('a', attrs=notfile | always) assert scheduler.update(a) with pytest.raises(scheduler.DependencyError): b = artefact('b', attrs=notfile) depend(a, b)
def test_fail(): """Check that an artefact won't be updated if a dependent artefact's recipe failed.""" a = artefact('a', attrs=notfile | always) b = artefact('b', attrs=notfile) fail = action('failing', 'fail') a = rule(fail, a) b = rule(pyecho, b, a) with patch('faber.scheduler._report_recipe'): assert not scheduler.update(b)
def test_nocare(): """Check that an artefact will be updated if a dependent artefact's recipe failed but was marked as nocare.""" a = artefact('a', attrs=notfile | always | nocare) b = artefact('b', attrs=notfile) fail = action('failing', 'fail') a = rule(fail, a) b = rule(pyecho, b, a) with patch('faber.scheduler._report_recipe') as recipe: assert scheduler.update(b) (_, _, _, _, _, output, _), kwds = recipe.call_args_list[-1] assert output.strip() == 'b <- a'
def test_call(): a = action() b = artefact('b', attrs=notfile) c = artefact('c', attrs=notfile) with pytest.raises(ValueError) as e: a(b, c) assert 'not implemented' in str(e.value) with capture_output() as (out, err): a = action('echo', 'echo $(<)') a([b]) assert out.getvalue().strip() == 'test.b' assert err.getvalue() == ''
def test_action(): """Check that an action's status is reported upon completion.""" a = artefact('a', attrs=notfile | always) b = artefact('b', attrs=notfile) c = artefact('c', attrs=notfile) a = rule(pyecho, a) b = rule(pyecho, b, a) c = rule(pyecho, c, b) with patch('faber.scheduler._report_recipe') as recipe: assert scheduler.update(b) (_, _, _, _, _, output, _), kwds = recipe.call_args_list[-1] assert output.strip() == 'b <- a' assert scheduler.update(c) (_, _, _, _, _, output, _), kwds = recipe.call_args_list[-1] assert output.strip() == 'c <- b'
def test_late_cycle(): """Test that the scheduler detects dependency cycles created in recipes.""" a = artefact('a', attrs=notfile | always) b = artefact('b', attrs=notfile | always) c = artefact('c', attrs=notfile | always) def generator(targets, sources): depend(b, c) # create cycle ! b = rule(generator, b, a) echo = action('echo', 'echo $(<) $(>)') c = rule(echo, c, b) with pytest.raises(scheduler.DependencyError): assert scheduler.update(c)
def test_recipe(): """Check that an artefact's __recipe__ method is called to report the execution of the recipe updating it.""" a = artefact('a', attrs=notfile | always) b = artefact('b', attrs=notfile | always) c = artefact('c', attrs=notfile | always) a = rule(pyecho, a) b = rule(pyecho, b, a) c = rule(pyecho, c, b) with patch('faber.action.action.__status__') as recipe: assert b.update() (_, _, _, _, output, _), kwds = recipe.call_args_list[-1] assert output.strip() == 'b <- a' assert c.update() (_, _, _, _, output, _), kwds = recipe.call_args_list[-1] assert output.strip() == 'c <- b'
def test_composite(): """Test the workflow of faber.artefacts.binary""" src = rule(fileutils.touch, 'src') bin = artefact('bin') def assemble(targets, sources): obj = rule(fileutils.copy, 'obj', src, attrs=intermediate, module=targets[0].module) rule(fileutils.copy, bin, obj, attrs=intermediate, module=targets[0].module) def test(targets, sources): print('testing {}'.format(sources[0].name)) assert exists(sources[0]._filename) # make a dependency graph ass = rule(assemble, 'ass', attrs=notfile | always) # make a binary depend(bin, ass) # and test it test = rule(test, 'test', bin, attrs=notfile) with patch('faber.scheduler._report_recipe') as recipe: scheduler.update([test]) (_, _, status, _, _, output, _), kwds = recipe.call_args_list[-1] assert output == 'testing bin\n' assert status == 0
def test_cycle(): """Test that the scheduler detects dependency cycles.""" echo = action('echo', 'echo $(<) $(>)') a = artefact('a', attrs=notfile | always) b = rule(echo, 'b', a, attrs=notfile) with pytest.raises(scheduler.DependencyError): a = rule(echo, a, b)
def test_conditional_dependency(): """Test the workflow of conditional dependencies""" name = feature('name', attributes=multi|path|incidental) # artefact to compute the condition a = rule(None, 'a', attrs=notfile|always) fs = set(name(a.filename)) # conditional artefact b1 = rule(pyecho, 'b1', features=fs, attrs=notfile|always) b2 = rule(pyecho, 'b2', features=fs, attrs=notfile|always) # c gets built unconditionally # but will include b1 or b2 only if condition is met c = artefact('c', attrs=notfile) c = rule(pyecho, c, dependencies=[placeholder(c, b1, set.name == ''), placeholder(c, b2, set.name != '')]) with patch('faber.action.action.__status__') as recipe: scheduler.update(c) output = [i[0][4].strip() for i in recipe.call_args_list] assert 'b1 <-' not in output and 'b2 <-' in output
def test_dynamic_dependencies(): """Test whether it's possible to add dependencies while the scheduler is already running.""" c = artefact('c', attrs=notfile) def inject_deps(self, *args, **kwds): d = rule(pyecho, 'd', attrs=notfile | always) depend(c, d) print('ddeps.b') a = rule(pyecho, 'a', attrs=notfile | always) b = rule(action('dg', inject_deps), 'b', a, attrs=notfile) c = rule(pyecho, c, b) with patch('faber.scheduler._report_recipe') as recipe: assert scheduler.update(b) (_, _, _, _, _, output, _), kwds = recipe.call_args_list[-1] assert output.strip() == 'ddeps.b' assert scheduler.update(c) # verify that d is actually updated before c output = [i[0][5].strip() for i in recipe.call_args_list] assert 'd <-' in output
def test_dynamic_recipe(): """Test whether it's possible to add a recipe while the scheduler is already running.""" c = artefact('c', attrs=notfile) def generate(*args, **kwds): """generate the graph for c.""" a = rule(pyecho, 'a', attrs=notfile | always) a1 = rule(pyecho, 'a1', a, attrs=notfile | always) a2 = rule(pyecho, 'a2', a1, attrs=notfile | always) rule( pyecho, c, a2, ) b = rule(generate, 'b', attrs=notfile | always) depend(c, b) with patch('faber.scheduler._report_recipe') as recipe: assert scheduler.update(c) output = [i[0][5].strip() for i in recipe.call_args_list] assert output[-1] == 'c <- a2'
def test_multi(): """TBD""" # workflow: # b1 needs to be updated, but doing that also updates b2 # this means c2 (dependent on b2) will also be updated. a = artefact('a', attrs=notfile) b1 = artefact('b1', attrs=notfile) b2 = artefact('b2', attrs=notfile | always) c1 = artefact('c1', attrs=notfile) c2 = artefact('c2', attrs=notfile) d = artefact('d', attrs=notfile) rule(pyecho, a) rule(pyecho, [b1, b2], a) rule(pyecho, c1, b1) rule(pyecho, c2, b2) rule(pyecho, d, [c1, c2]) with patch('faber.scheduler._report_recipe') as recipe: assert scheduler.update(d) output = [i[0][4].strip() for i in recipe.call_args_list] assert output[0] == 'b1 b2 <- a' assert 'c2 <- b2' in output # sets aren't ordered, so we check for both possibilities assert output[-1] in ('d <- c1 c2', 'd <- c2 c1')