blob: 51e9e5ff969682385392af13bf96ecb84c27df50 [file]
#!/usr/bin/env vpython3
# Copyright 2018 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
from __future__ import annotations
import test_env
import turboci_test_helper
from google.protobuf.message import Message
from google.protobuf.any_pb2 import Any
from google.protobuf.timestamp_pb2 import Timestamp
from google.protobuf.struct_pb2 import ListValue, Struct, Value
from PB.turboci.graph.ids.v1 import identifier
from PB.turboci.graph.orchestrator.v1.check import Check
from PB.turboci.graph.orchestrator.v1.check_kind import CheckKind
from PB.turboci.graph.orchestrator.v1.check_state import CheckState
from PB.turboci.graph.orchestrator.v1.dependencies import Dependencies
from PB.turboci.graph.orchestrator.v1.edge import RESOLUTION_SATISFIED, Edge
from PB.turboci.graph.orchestrator.v1.query import Query
from PB.turboci.graph.orchestrator.v1.revision import Revision
from PB.turboci.graph.orchestrator.v1.value_ref import ValueRef
from recipe_engine import turboci
from recipe_engine.turboci import dep_group, check_id
from recipe_engine.internal.turboci.common import get_check_by_full_id
from recipe_engine.internal.turboci.fake import _IndexEntrySnapshot
from recipe_engine.internal.turboci.ids import AnyIdentifier, type_url_for, type_urls
demoStruct = Struct(fields={'hello': Value(string_value='world')})
demoStruct2 = Struct(fields={'hola': Value(string_value='mundo')})
demoTS = Timestamp(seconds=100, nanos=100)
demoTS2 = Timestamp(seconds=200, nanos=200)
structURL = type_url_for(demoStruct)
tsURL = type_url_for(demoTS)
def _mkAny(value: Message) -> Any:
ret = Any()
ret.Pack(value, deterministic=True)
return ret
def _mkValueRef(value: Message, version: Revision) -> ValueRef:
ret = ValueRef(type_url=type_url_for(value))
ret.inline.binary.Pack(value, deterministic=True)
return ret
def _mkOptions(id: str,
*msg: type[Message] | Message,
in_workplan: str = "") -> list[ValueRef]:
ret = []
for i, m in enumerate(msg):
value_ref = ValueRef(type_url=type_url_for(m))
if isinstance(m, type):
m = m()
value_ref.inline.binary.Pack(m, deterministic=True)
ret.append(value_ref)
return ret
def _mkResults(id: str,
*msg: type[Message] | Message,
in_workplan: str = "",
result_idx: int = 1) -> list[ValueRef]:
ident = check_id(id, in_workplan=in_workplan)
ret = []
for i, m in enumerate(msg):
value_ref = ValueRef(type_url=type_url_for(m))
if isinstance(m, type):
m = m()
value_ref.inline.binary.Pack(m, deterministic=True)
ret.append(value_ref)
return ret
class IndexEntrySnapshotTest(test_env.RecipeEngineUnitTest):
def test_snapshot(self):
ident = check_id('thing')
check = Check(
identifier=ident,
kind='CHECK_KIND_TEST',
state='CHECK_STATE_WAITING',
options=_mkOptions('thing', Struct, Value, ListValue),
results=[
Check.Result(data=_mkResults('thing', Struct, Value),),
],
)
entry = _IndexEntrySnapshot.for_check(check)
self.assertEqual(entry.kind, CheckKind.CHECK_KIND_TEST)
self.assertEqual(entry.state, CheckState.CHECK_STATE_WAITING)
self.assertEqual(
entry.option_types, {
'type.googleapis.com/google.protobuf.Struct',
'type.googleapis.com/google.protobuf.Value',
'type.googleapis.com/google.protobuf.ListValue',
})
self.assertEqual(
entry.result_types, {
'type.googleapis.com/google.protobuf.Struct',
'type.googleapis.com/google.protobuf.Value',
})
def test_empty_snapshot(self):
entry = _IndexEntrySnapshot.for_check(None)
self.assertEqual(entry.kind, 0)
self.assertEqual(entry.state, 0)
self.assertEqual(entry.option_types, set())
self.assertEqual(entry.result_types, set())
class SimpleTurboCIFakeTest(turboci_test_helper.TestBaseClass):
def test_single_check_write(self):
self.write_nodes(
turboci.check(
'hey',
kind='CHECK_KIND_BUILD',
options=[demoStruct],
))
rslt = self.query_nodes(
turboci.make_query(
Query.CollectChecks(options=True),
node_set=turboci.collect_check_ids('hey'),
),
types=[demoStruct]).workplans[0]
self.assertEqual(len(rslt.checks), 1)
# Check that option data is correct
check = get_check_by_full_id(rslt, 'L:Chey')
self.assertEqual(len(check.options), 1)
self.assertEqual(check.options[0], _mkValueRef(
demoStruct,
rslt.version,
))
self.assertEqual(
check,
Check(
identifier=turboci.check_id('hey'),
state='CHECK_STATE_PLANNING',
state_history=[
Check.StateHistoryEntry(
state='CHECK_STATE_PLANNING',
version=rslt.version,
),
],
kind='CHECK_KIND_BUILD',
version=rslt.version,
options=check.options, # we verified contents above
dependencies=Dependencies(),
))
def test_check_state_PLANNING_add_option(self):
self.write_nodes(turboci.check(
'hey',
kind='CHECK_KIND_BUILD',
))
self.write_nodes(turboci.check(
'hey',
options=[demoStruct],
))
rslt = self.read_checks(
'hey',
collect=Query.CollectChecks(options=True),
types=[Struct],
)[0]
self.assertEqual(len(rslt.options), 1)
self.assertEqual(rslt.options[0].inline.binary, _mkAny(demoStruct))
def test_check_state_PLANNING_overwrite_option(self):
self.write_nodes(
turboci.check(
'hey',
kind='CHECK_KIND_BUILD',
options=[demoStruct],
))
self.write_nodes(turboci.check(
'hey',
options=[demoStruct2],
))
rslt = self.read_checks(
'hey',
collect=Query.CollectChecks(options=True),
types=[Struct],
)[0]
self.assertEqual(len(rslt.options), 1)
self.assertEqual(rslt.options[0].inline.binary, _mkAny(demoStruct2))
def test_check_state_PLANNING_add_second_option(self):
self.write_nodes(
turboci.check(
'hey',
kind='CHECK_KIND_BUILD',
options=[demoStruct],
))
self.write_nodes(turboci.check(
'hey',
options=[demoTS],
))
rslt = self.read_checks(
'hey',
collect=Query.CollectChecks(options=True),
types=[Struct, Timestamp],
)[0]
self.assertEqual(len(rslt.options), 2)
# The order depends on implementation details (append), but verify existence
vals = {o.type_url: o.inline.binary for o in rslt.options}
self.assertEqual(vals[structURL], _mkAny(demoStruct))
self.assertEqual(vals[tsURL], _mkAny(demoTS))
def test_check_state_PLANNING_add_dependency(self):
self.write_nodes(
turboci.check(
'hey',
kind='CHECK_KIND_BUILD',
options=[demoStruct],
# Note: we can add a dependency to a check which we are writing
# concurrently with 'hey'.
deps=dep_group('there'),
),
turboci.check(
'there',
kind='CHECK_KIND_BUILD',
options=[demoStruct],
))
rslt = self.read_checks('hey')[0]
self.assertEqual(len(rslt.dependencies.edges), 1)
self.assertEqual(rslt.dependencies.edges[0],
Edge(check=Edge.Check(identifier=check_id('there'))))
def test_error_check_state_missing_dep(self):
with self.assertRaisesRegex(turboci.InvalidArgumentException,
"unsatisfiable dependencies"):
self.write_nodes(
turboci.check(
'hey',
kind='CHECK_KIND_BUILD',
deps=dep_group('missing'),
))
def test_check_state_PLANNING_replace_dependency(self):
self.write_nodes(
turboci.check(
'hey',
kind='CHECK_KIND_BUILD',
options=[demoStruct],
# Note: we can add a dependency to a check which we are writing
# concurrently with 'hey'.
deps=dep_group('there'),
),
turboci.check(
'there',
kind='CHECK_KIND_BUILD',
options=[demoStruct],
))
self.write_nodes(
turboci.check(
'hey',
deps=dep_group('there', 'moo'),
), turboci.check(
'moo',
kind='CHECK_KIND_BUILD',
))
rslt = self.read_checks('hey')[0]
self.assertEqual(len(rslt.dependencies.edges), 2)
self.assertListEqual(
list(rslt.dependencies.edges), [
Edge(check=Edge.Check(identifier=check_id('there'))),
Edge(check=Edge.Check(identifier=check_id('moo'))),
])
self.assertEqual(rslt.dependencies.predicate,
Dependencies.Group(edges=[0, 1]))
def test_check_state_PLANNING_evolve(self):
self.write_nodes(
turboci.check(
'hey',
kind='CHECK_KIND_BUILD',
options=[demoStruct],
))
# update options and mark PLANNED
self.write_nodes(
turboci.check(
'hey',
options=[demoStruct2],
state='CHECK_STATE_PLANNED',
))
with self.assertRaises(turboci.CheckWriteInvariantException):
# Can't write options any more
self.write_nodes(turboci.check(
'hey',
options=[demoTS],
))
rslt = self.read_checks('hey')[0]
# This had no dependencies so goes straight to WAITING.
self.assertEqual(rslt.state, CheckState.CHECK_STATE_WAITING)
def test_check_state_PLANNED_start(self):
self.write_nodes(
turboci.check(
'hey',
kind='CHECK_KIND_BUILD',
options=[demoStruct],
state='CHECK_STATE_PLANNED',
))
with self.assertRaises(turboci.CheckWriteInvariantException):
# Can't write options any more
self.write_nodes(turboci.check(
'hey',
options=[demoStruct2],
))
rslt = self.read_checks('hey')[0]
# This had no dependencies so goes straight to WAITING.
self.assertEqual(rslt.state, CheckState.CHECK_STATE_WAITING)
def test_check_state_PLANNED_with_deps(self):
self.write_nodes(
turboci.check(
'hey',
kind='CHECK_KIND_BUILD',
options=[demoStruct],
# Note: we can add a dependency to a check which we are writing
# concurrently with 'hey'.
deps=dep_group('there'),
),
turboci.check(
'there',
kind='CHECK_KIND_BUILD',
options=[demoStruct],
))
self.write_nodes(turboci.check(
'hey',
state='CHECK_STATE_PLANNED',
))
rslt = self.read_checks('hey')[0]
self.assertEqual(rslt.state, CheckState.CHECK_STATE_PLANNED)
# completing `there` unblocks `hey`.
self.write_nodes(turboci.check(
'there',
state='CHECK_STATE_FINAL',
))
rslt = self.read_checks('hey')[0]
self.assertEqual(rslt.state, CheckState.CHECK_STATE_WAITING)
def test_check_linear_chain(self):
self.write_nodes(
turboci.check(
'a',
kind='CHECK_KIND_BUILD',
deps=dep_group('b'),
), turboci.check(
'b',
kind='CHECK_KIND_BUILD',
deps=dep_group('c'),
), turboci.check(
'c',
kind='CHECK_KIND_BUILD',
))
ret = self.read_checks('a')[0]
self.assertEqual(ret.state, CheckState.CHECK_STATE_PLANNING)
self.write_nodes(
turboci.check(
'a',
state='CHECK_STATE_PLANNED',
), turboci.check(
'b',
state='CHECK_STATE_PLANNED',
))
ret = self.read_checks('a')[0]
self.assertEqual(ret.state, CheckState.CHECK_STATE_PLANNED)
self.write_nodes(turboci.check(
'c',
state='CHECK_STATE_FINAL',
))
ret = self.read_checks('b')[0]
self.assertEqual(ret.state, CheckState.CHECK_STATE_WAITING)
self.write_nodes(turboci.check(
'b',
state='CHECK_STATE_FINAL',
))
ret = self.read_checks('a')[0]
self.assertEqual(ret.state, CheckState.CHECK_STATE_WAITING)
def test_check_add_dep_to_FINAL(self):
self.write_nodes(
turboci.check(
'a',
kind='CHECK_KIND_BUILD',
),
turboci.check(
'b',
kind='CHECK_KIND_BUILD',
state='CHECK_STATE_FINAL',
))
ret = self.read_checks('a')[0]
self.assertEqual(ret.state, CheckState.CHECK_STATE_PLANNING)
self.write_nodes(
turboci.check(
'a',
state='CHECK_STATE_PLANNED',
deps=dep_group('b'),
))
ret = self.read_checks('a')[0]
self.assertEqual(ret.state, CheckState.CHECK_STATE_WAITING)
def test_check_PLANNING_to_FINAL(self):
self.write_nodes(turboci.check(
'hey',
kind='CHECK_KIND_BUILD',
))
# You can go directly from PLANNING->FINAL if there are no unresolved
# dependencies.
self.write_nodes(
turboci.check(
'hey',
state='CHECK_STATE_FINAL',
results=[demoStruct],
))
rslt = self.read_checks(
'hey',
collect=Query.CollectChecks(options=True, result_data=True),
types=[demoStruct],
)[0]
self.assertEqual(rslt.state, CheckState.CHECK_STATE_FINAL)
self.assertEqual(rslt.results[0].data[0].type_url,
turboci.type_url_for(demoStruct))
self.assertTrue(rslt.results[0].HasField('created_at'))
self.assertTrue(rslt.results[0].HasField('finalized_at'))
self.assertEqual(rslt.results[0].data[0].inline.binary, _mkAny(demoStruct))
def test_check_WAITING_results(self):
self.write_nodes(
turboci.check(
'hey',
kind='CHECK_KIND_BUILD',
state=CheckState.CHECK_STATE_WAITING,
))
rslt = self.read_checks('hey')[0]
self.assertEqual(rslt.state, CheckState.CHECK_STATE_WAITING)
self.write_nodes(turboci.check(
'hey',
results=[demoStruct],
))
rslt = self.read_checks(
'hey',
collect=Query.CollectChecks(result_data=True),
types=[demoStruct],
)[0]
self.assertEqual(rslt.results[0].data[0].type_url,
turboci.type_url_for(demoStruct))
self.assertTrue(rslt.results[0].HasField('created_at'))
created_ts = rslt.results[0].created_at
self.assertFalse(rslt.results[0].HasField('finalized_at'))
self.write_nodes(turboci.check(
'hey',
finalize_results=True,
))
rslt = self.read_checks('hey')[0]
self.assertTrue(rslt.results[0].HasField('finalized_at'))
finalized_ts = rslt.results[0].finalized_at
self.assertGreater((finalized_ts.ts.seconds, finalized_ts.ts.nanos),
(created_ts.ts.seconds, created_ts.ts.nanos))
def test_query_filter_kind(self):
self.write_nodes(
turboci.check('a', kind='CHECK_KIND_ANALYSIS'),
turboci.check('b', kind='CHECK_KIND_SOURCE'),
turboci.check('c', kind='CHECK_KIND_BUILD'),
turboci.check('cc', kind='CHECK_KIND_BUILD'),
)
ret = self.query_nodes(
turboci.make_query(
Query.SelectChecks.Predicate(kind='CHECK_KIND_ANALYSIS'),
Query.SelectChecks.Predicate(kind=CheckKind.CHECK_KIND_BUILD),
)).workplans[0]
self.assertEqual(len(ret.checks), 3)
self.assertEqual(self.check_ids(ret.checks), {'L:Ca', 'L:Cc', 'L:Ccc'})
def test_query_filter_option(self):
self.write_nodes(
turboci.check('a', kind='CHECK_KIND_ANALYSIS', options=[demoStruct]),
turboci.check('b', kind='CHECK_KIND_ANALYSIS', options=[demoTS]),
turboci.check('c', kind='CHECK_KIND_ANALYSIS', options=[demoStruct]),
)
ret = self.query_nodes(
turboci.make_query(
Query.SelectChecks.Predicate(
with_option_type=turboci.type_set(demoStruct)),)).workplans[0]
self.assertEqual(len(ret.checks), 2)
self.assertEqual(self.check_ids(ret.checks), {'L:Ca', 'L:Cc'})
def test_query_filter_all_options(self):
self.write_nodes(
turboci.check('a', kind='CHECK_KIND_ANALYSIS', options=[demoStruct]),
turboci.check('b', kind='CHECK_KIND_ANALYSIS', options=[demoTS]),
turboci.check('c', kind='CHECK_KIND_ANALYSIS', options=[demoStruct]),
)
ret = self.query_nodes(
turboci.make_query(
Query.SelectChecks(),
Query.CollectChecks(options=True),
),
types=('*',)).workplans[0]
self.assertEqual(len(ret.checks), 3)
self.assertEqual(self.check_ids(ret.checks), {'L:Ca', 'L:Cb', 'L:Cc'})
types = set()
for check in ret.checks:
types.update(o.type_url for o in check.options)
self.assertEqual(types, set(type_urls(demoStruct, demoTS)))
def test_query_filter_result(self):
self.write_nodes(
turboci.check(
'a',
kind='CHECK_KIND_ANALYSIS',
results=[demoStruct],
state='CHECK_STATE_FINAL'),
turboci.check(
'b',
kind='CHECK_KIND_ANALYSIS',
results=[demoTS],
state='CHECK_STATE_FINAL'),
turboci.check(
'c',
kind='CHECK_KIND_ANALYSIS',
results=[demoStruct],
state='CHECK_STATE_FINAL'),
)
ret = self.query_nodes(
turboci.make_query(
Query.SelectChecks.Predicate(
with_result_data_type=turboci.type_set(demoStruct)),
)).workplans[0]
self.assertEqual(len(ret.checks), 2)
self.assertEqual(self.check_ids(ret.checks), {'L:Ca', 'L:Cc'})
def test_query_filter_follow_down(self):
# make a simple diamond
self.write_nodes(
turboci.check(
'a',
kind='CHECK_KIND_ANALYSIS',
deps=dep_group('b', 'c'),
), turboci.check(
'b',
kind='CHECK_KIND_TEST',
deps=dep_group('d'),
), turboci.check(
'c',
kind='CHECK_KIND_TEST',
deps=dep_group('d'),
), turboci.check(
'd',
kind='CHECK_KIND_BUILD',
deps=dep_group('s'),
),
turboci.check(
's',
kind='CHECK_KIND_SOURCE',
state='CHECK_STATE_FINAL',
))
ret = self.query_nodes(
turboci.make_query(
Query.SelectChecks.Predicate(kind='CHECK_KIND_ANALYSIS'),
Query.ExpandDependencies(),
)).workplans[0]
self.assertEqual(self.check_ids(ret.checks), {'L:Ca', 'L:Cb', 'L:Cc'})
def test_query_filter_follow_up(self):
# make a simple diamond
self.write_nodes(
turboci.check(
'a',
kind='CHECK_KIND_ANALYSIS',
deps=dep_group('b', 'c'),
), turboci.check(
'b',
kind='CHECK_KIND_TEST',
deps=dep_group('d'),
), turboci.check(
'c',
kind='CHECK_KIND_TEST',
deps=dep_group('d'),
), turboci.check(
'd',
kind='CHECK_KIND_BUILD',
deps=dep_group('s'),
),
turboci.check(
's',
kind='CHECK_KIND_SOURCE',
state='CHECK_STATE_FINAL',
))
ret = self.query_nodes(
turboci.make_query(
Query.SelectChecks.Predicate(kind=CheckKind.CHECK_KIND_SOURCE),
Query.ExpandDependents(),
)).workplans[0]
self.assertEqual(self.check_ids(ret.checks), {'L:Cs', 'L:Cd'})
def test_dependencies_edges(self):
self.write_nodes(
turboci.check('A', kind='CHECK_KIND_BUILD', state='CHECK_STATE_FINAL'),
turboci.check(
'B',
kind='CHECK_KIND_BUILD',
state='CHECK_STATE_PLANNED',
deps=dep_group('A')),
turboci.check(
'C',
kind='CHECK_KIND_BUILD',
state='CHECK_STATE_PLANNED',
deps=dep_group('A')),
turboci.check(
'D',
kind='CHECK_KIND_BUILD',
state='CHECK_STATE_PLANNED',
deps=dep_group('B', 'C')),
)
ret = self.query_nodes(
turboci.make_query(
Query.ExpandDependencies(),
node_set=turboci.collect_check_ids('D'),
)).workplans[0]
self.assertEqual(self.check_ids(ret.checks), {'L:CD', 'L:CB', 'L:CC'})
def test_dependencies_satisfied(self):
self.write_nodes(
turboci.check('A', kind='CHECK_KIND_BUILD', state='CHECK_STATE_FINAL'),
turboci.check(
'B',
kind='CHECK_KIND_BUILD',
state='CHECK_STATE_PLANNED',
deps=dep_group('A')),
turboci.check(
'C',
kind='CHECK_KIND_BUILD',
state='CHECK_STATE_PLANNED',
deps=dep_group('A')),
turboci.check(
'D',
kind='CHECK_KIND_BUILD',
state='CHECK_STATE_PLANNED',
deps=dep_group('B', 'C')),
)
# d has no satisfied dependencies
ret = self.query_nodes(
turboci.make_query(
Query.ExpandDependencies(mode='QUERY_EXPAND_DEPS_MODE_SATISFIED'),
node_set=turboci.collect_check_ids('D'),
)).workplans[0]
self.assertEqual(self.check_ids(ret.checks), {'L:CD'})
# Once B and C are final, D should be unblocked
self.write_nodes(
turboci.check('B', state='CHECK_STATE_FINAL'),
turboci.check('C', state='CHECK_STATE_FINAL'),
)
# deps of d are satisfied now
ret = self.query_nodes(
turboci.make_query(
Query.ExpandDependencies(mode='QUERY_EXPAND_DEPS_MODE_SATISFIED'),
node_set=turboci.collect_check_ids('D'),
)).workplans[0]
self.assertEqual(self.check_ids(ret.checks), {'L:CD', 'L:CB', 'L:CC'})
self.assertEqual(
get_check_by_full_id(ret, 'L:CD').state, CheckState.CHECK_STATE_WAITING)
def test_dependents_edges(self):
self.write_nodes(
turboci.check('A', kind='CHECK_KIND_BUILD'),
turboci.check(
'B',
kind='CHECK_KIND_BUILD',
state='CHECK_STATE_PLANNED',
deps=dep_group('A')),
turboci.check(
'C',
kind='CHECK_KIND_BUILD',
state='CHECK_STATE_PLANNED',
deps=dep_group('A')),
turboci.check(
'D',
kind='CHECK_KIND_BUILD',
state='CHECK_STATE_PLANNED',
deps=dep_group('B', 'C')),
)
ret = self.query_nodes(
turboci.make_query(
Query.ExpandDependents(),
node_set=turboci.collect_check_ids('A'),
)).workplans[0]
self.assertEqual(self.check_ids(ret.checks), {'L:CA', 'L:CB', 'L:CC'})
def test_dependents_satisfied(self):
self.write_nodes(
turboci.check('A', kind='CHECK_KIND_BUILD'),
turboci.check(
'B',
kind='CHECK_KIND_BUILD',
state='CHECK_STATE_PLANNED',
deps=dep_group('A')),
turboci.check(
'C',
kind='CHECK_KIND_BUILD',
state='CHECK_STATE_PLANNED',
deps=dep_group('A')),
turboci.check(
'D',
kind='CHECK_KIND_BUILD',
state='CHECK_STATE_PLANNED',
deps=dep_group('B', 'C')),
)
ret = self.query_nodes(
turboci.make_query(
Query.ExpandDependents(mode='QUERY_EXPAND_DEPS_MODE_SATISFIED'),
node_set=turboci.collect_check_ids('A'),
)).workplans[0]
self.assertEqual(self.check_ids(ret.checks), {'L:CA'})
self.write_nodes(turboci.check('A', state='CHECK_STATE_FINAL'))
ret = self.query_nodes(
turboci.make_query(
Query.ExpandDependents(mode='QUERY_EXPAND_DEPS_MODE_SATISFIED'),
node_set=turboci.collect_check_ids('A'),
)).workplans[0]
self.assertEqual(self.check_ids(ret.checks), {'L:CA', 'L:CB', 'L:CC'})
def test_ab_bc_resolution(self):
self.write_nodes(
turboci.check(
'p',
kind='CHECK_KIND_BUILD',
state='CHECK_STATE_PLANNED',
deps=dep_group(
dep_group('a', 'b'),
dep_group('b', 'c'),
dep_group('c', 'd'),
threshold=1,
)),
turboci.check('a', kind='CHECK_KIND_BUILD'),
turboci.check('b', kind='CHECK_KIND_BUILD'),
turboci.check('c', kind='CHECK_KIND_BUILD'),
turboci.check('d', kind='CHECK_KIND_BUILD'),
)
p = self.read_checks('p')[0]
self.assertFalse(p.dependencies.HasField('resolution'))
# satisfy p->a and p->c
self.write_nodes(
turboci.check('a', state='CHECK_STATE_FINAL'),
turboci.check('c', state='CHECK_STATE_FINAL'),
)
# still no resolution yet
p = self.read_checks('p')[0]
self.assertFalse(p.dependencies.HasField('resolution'))
# satisfy p->d
self.write_nodes(turboci.check('d', state='CHECK_STATE_FINAL'),)
# resolved
p = self.read_checks('p')[0]
self.assertTrue(p.dependencies.HasField('resolution'))
# and satisfied has the minimal set of just ['c', 'd']
self.assertEqual(p.dependencies.resolution, RESOLUTION_SATISFIED)
def test_check_versioning_invariant(self):
# 1. Create Check with Option
self.write_nodes(
turboci.check(
'check',
kind='CHECK_KIND_BUILD',
options=[demoStruct],
))
check = self.read_checks(
'check', collect=Query.CollectChecks(options=True),
types=[demoStruct])[0]
check_v1 = check.version
# 2. Modify Option
self.write_nodes(turboci.check(
'check',
options=[demoStruct2],
))
check = self.read_checks(
'check', collect=Query.CollectChecks(options=True),
types=[demoStruct])[0]
check_v2 = check.version
# Check version SHOULD change
self.assertGreater((check_v2.ts.seconds, check_v2.ts.nanos),
(check_v1.ts.seconds, check_v1.ts.nanos))
# 3. Add Result (Check version should change because we added a result)
self.write_nodes(
turboci.check(
'check',
state='CHECK_STATE_WAITING',
results=[demoStruct],
))
check = self.read_checks(
'check',
collect=Query.CollectChecks(result_data=True),
types=[demoStruct])[0]
check_v3 = check.version
self.assertGreater((check_v3.ts.seconds, check_v3.ts.nanos),
(check_v2.ts.seconds, check_v2.ts.nanos))
# 4. Modify Result
self.write_nodes(turboci.check(
'check',
results=[demoStruct2],
))
check = self.read_checks(
'check',
collect=Query.CollectChecks(result_data=True),
types=[demoStruct])[0]
check_v4 = check.version
# Check version SHOULD change
self.assertGreater((check_v4.ts.seconds, check_v4.ts.nanos),
(check_v3.ts.seconds, check_v3.ts.nanos))
def test_check_option_versioning(self):
# 1. Create Check with an initial Option
self.write_nodes(
turboci.check(
'versioned_check',
kind='CHECK_KIND_BUILD',
options=[demoStruct],
))
# Read back the initial state
check = self.read_checks(
'versioned_check',
collect=Query.CollectChecks(options=True),
types=[demoStruct])[0]
check_v1 = check.version
# 2. Modify the existing Option
self.write_nodes(turboci.check(
'versioned_check',
options=[demoStruct2],
))
# Read back the updated state
check = self.read_checks(
'versioned_check',
collect=Query.CollectChecks(options=True),
types=[demoStruct2])[0]
check_v2 = check.version
# Check version MUST change
self.assertGreater((check_v2.ts.seconds, check_v2.ts.nanos),
(check_v1.ts.seconds, check_v1.ts.nanos))
# 3. Adding a NEW option SHOULD change the check version
self.write_nodes(turboci.check(
'versioned_check',
options=[demoStruct2, demoTS],
))
check = self.read_checks(
'versioned_check',
collect=Query.CollectChecks(options=True),
types=[demoStruct2, demoTS])[0]
check_v3 = check.version
self.assertGreater((check_v3.ts.seconds, check_v3.ts.nanos),
(check_v2.ts.seconds, check_v2.ts.nanos))
def test_query_check_no_options(self):
self.write_nodes(
turboci.check('A', kind='CHECK_KIND_BUILD', options=[demoStruct]))
# Query check 'A' without requesting options data
ret = self.read_checks('A')
check = ret[0]
self.assertEqual(len(check.options), 1)
# Check that value is stripped but type_url is present
self.assertEqual(check.options[0].type_url,
turboci.type_url_for(demoStruct))
self.assertFalse(check.options[0].inline.binary.value)
def test_check_state_history(self):
# 1. Create in PLANNING
self.write_nodes(
turboci.check(
'check',
kind='CHECK_KIND_BUILD',
deps=dep_group('dep'),
),
turboci.check(
'dep',
kind='CHECK_KIND_BUILD',
)
)
check = self.read_checks('check')[0]
self.assertEqual(check.state, CheckState.CHECK_STATE_PLANNING)
self.assertEqual(len(check.state_history), 1)
self.assertEqual(check.state_history[0].state,
CheckState.CHECK_STATE_PLANNING)
v1 = check.state_history[0].version
# 2. Move to PLANNED
self.write_nodes(turboci.check(
'check',
state='CHECK_STATE_PLANNED',
))
check = self.read_checks('check')[0]
self.assertEqual(check.state, CheckState.CHECK_STATE_PLANNED)
self.assertEqual(len(check.state_history), 2)
self.assertEqual(check.state_history[0].state,
CheckState.CHECK_STATE_PLANNING)
self.assertEqual(check.state_history[1].state,
CheckState.CHECK_STATE_PLANNED)
v2 = check.state_history[1].version
self.assertGreater((v2.ts.seconds, v2.ts.nanos),
(v1.ts.seconds, v1.ts.nanos))
# 3. Move 'dep' to FINAL -> 'check' becomes WAITING
self.write_nodes(turboci.check(
'dep',
state='CHECK_STATE_FINAL',
))
check = self.read_checks('check')[0]
self.assertEqual(check.state, CheckState.CHECK_STATE_WAITING)
self.assertEqual(len(check.state_history), 3)
self.assertEqual(check.state_history[2].state,
CheckState.CHECK_STATE_WAITING)
v3 = check.state_history[2].version
self.assertGreater((v3.ts.seconds, v3.ts.nanos),
(v2.ts.seconds, v2.ts.nanos))
# 4. Move to FINAL
self.write_nodes(turboci.check(
'check',
state='CHECK_STATE_FINAL',
))
check = self.read_checks('check')[0]
self.assertEqual(check.state, CheckState.CHECK_STATE_FINAL)
self.assertEqual(len(check.state_history), 4)
self.assertEqual(check.state_history[3].state, CheckState.CHECK_STATE_FINAL)
v4 = check.state_history[3].version
self.assertGreater((v4.ts.seconds, v4.ts.nanos),
(v3.ts.seconds, v3.ts.nanos))
if __name__ == '__main__':
test_env.main()