blob: 5cac71e523f8fc470de1f68f54278eb1d183a8aa [file]
#!/usr/bin/env vpython3
# Copyright 2025 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
from typing import cast
from unittest.mock import MagicMock
from google.protobuf.timestamp_pb2 import Timestamp
import test_env
import turboci_test_helper
from google.protobuf.message import Message
from recipe_engine.internal.turboci.ids import type_urls
from google.protobuf.struct_pb2 import Struct, Value as StructValue
from google.protobuf.proto_json import parse
from PB.turboci.graph.ids.v1 import identifier
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.edge import Edge
from PB.turboci.graph.orchestrator.v1.query import Query
from PB.turboci.graph.orchestrator.v1.query_nodes_request import QueryNodesRequest
from PB.turboci.graph.orchestrator.v1.revision import Revision
from PB.turboci.graph.orchestrator.v1.type_info import TypeInfo
from PB.turboci.graph.orchestrator.v1.type_set import TypeSet
from PB.turboci.graph.orchestrator.v1.value_ref import ValueRef
from PB.turboci.graph.orchestrator.v1.value_write import ValueWrite
from PB.turboci.graph.orchestrator.v1.write_nodes_request import WriteNodesRequest
from recipe_engine.internal.turboci import common
from recipe_engine.turboci import (write_nodes, reason, check, dep_group,
check_id, query_nodes, make_query,
get_option, get_results)
def _mkStruct(d: dict) -> Struct:
return cast(Struct, parse(Struct, d))
def _mkValue(msg: Message, realm: str | None = None) -> ValueWrite:
ret = ValueWrite()
if realm:
ret.realm = realm
ret.data.Pack(msg, deterministic=True)
return ret
class TestCheckID(test_env.RecipeEngineUnitTest):
def test_ok(self):
self.assertEqual(check_id('fleem'), identifier.Check(id='fleem'))
self.assertEqual(check_id('fleem', in_workplan='123'), identifier.Check(
work_plan=identifier.WorkPlan(id='123'),
id='fleem',
))
self.assertEqual(check_id('fleem', in_workplan='L321'), identifier.Check(
work_plan=identifier.WorkPlan(id='321'),
id='fleem',
))
def test_fail(self):
with self.assertRaisesRegex(ValueError, 'must not contain'):
check_id('not:cool')
with self.assertRaises(ValueError) as ex:
check_id('just fine', in_workplan='bad news')
self.assertRegex(ex.exception.__notes__[0], 'in_workplan: id must be parsable')
class TestReason(test_env.RecipeEngineUnitTest):
def test_ok(self):
r = reason("some string", _mkStruct({'a': 'b'}))
self.assertEqual(
r,
WriteNodesRequest.Reason(
message="some string",
details=[_mkValue(_mkStruct({'a': 'b'}))],
))
class TestDepGroup(test_env.RecipeEngineUnitTest):
def test_ok(self):
dg = dep_group(
'stuff',
identifier.Identifier(check=identifier.Check(id="things")),
identifier.Check(id="more"),
dep_group('nested-1', 'nested-2', in_workplan='L123456'),
threshold=3,
)
self.assertEqual(
dg,
WriteNodesRequest.DependencyGroup(
edges=[
Edge(check=Edge.Check(identifier=check_id('stuff'))),
Edge(check=Edge.Check(identifier=check_id('things'))),
Edge(check=Edge.Check(identifier=check_id('more'))),
],
groups=[
WriteNodesRequest.DependencyGroup(edges=[
Edge(check=Edge.Check(identifier=check_id(
'nested-1', in_workplan='123456'))),
Edge(check=Edge.Check(identifier=check_id(
'nested-2', in_workplan='123456'))),
]),
],
threshold=3,
))
class TestCheck(test_env.RecipeEngineUnitTest):
def test_ok(self):
chk = check(
'the check id',
kind='CHECK_KIND_TEST',
state='CHECK_STATE_PLANNED',
options=[_mkStruct({'a': 'b'})],
deps=dep_group(
"stuff",
"things",
),
results=[_mkStruct({'cool': ['result']})],
finalize_results=True,
in_workplan='321',
realm='project/check/realm',
realm_options=[
('project/check/option/realm',
StructValue(string_value='realm_option')),
],
realm_results=[
('project/check/result/realm',
StructValue(string_value='realm_result')),
],
)
self.assertEqual(
chk,
WriteNodesRequest.CheckWrite(
identifier=identifier.Check(
work_plan=identifier.WorkPlan(id='321'),
id='the check id',
),
realm='project/check/realm',
kind=CheckKind.CHECK_KIND_TEST,
options=[
_mkValue(_mkStruct({'a': 'b'})),
_mkValue(
StructValue(string_value='realm_option'),
realm='project/check/option/realm'),
],
dependencies=WriteNodesRequest.DependencyGroup(
edges=[
Edge(check=Edge.Check(identifier=check_id('stuff'))),
Edge(check=Edge.Check(identifier=check_id('things'))),
],),
results=[
_mkValue(_mkStruct({'cool': ['result']})),
_mkValue(
StructValue(string_value='realm_result'),
realm='project/check/result/realm'),
],
finalize_results=True,
state=CheckState.CHECK_STATE_PLANNED,
))
class TestWriteNodes(test_env.RecipeEngineUnitTest):
def setUp(self):
self.m = MagicMock()
common.CLIENT = self.m
return super().setUp()
def tearDown(self):
common.CLIENT = None
return super().tearDown()
def test_write_nodes(self):
# User writes:
write_nodes(
check(
"someid",
kind='CHECK_KIND_BUILD',
options=[
_mkStruct({"cool_opt": [1, 2, 3]}),
]),
reason("I feel like it", _mkStruct({"hello": "world"})),
)
# Raw API call to common.CLIENT.
self.m.WriteNodes.assert_called_once_with(
WriteNodesRequest(
reason=WriteNodesRequest.Reason(
message="I feel like it",
details=[_mkValue(_mkStruct({'hello': 'world'}))],
),
checks=[
WriteNodesRequest.CheckWrite(
identifier=identifier.Check(id="someid"),
kind=CheckKind.CHECK_KIND_BUILD,
options=[_mkValue(_mkStruct({'cool_opt': [1, 2, 3]}))],
),
],
))
def test_query_nodes(self):
query_nodes(
make_query(
node_set=[check_id("bob")],
), make_query(
Query.SelectChecks.Predicate(kind='CHECK_KIND_TEST'),
Query.CollectChecks(options=True),
),
version=QueryNodesRequest.VersionRestriction(
require=Revision(ts=Timestamp(seconds=1234, nanos=5678)),
))
self.m.QueryNodes.assert_called_once_with(
QueryNodesRequest(
type_info=TypeInfo(wanted=TypeSet()),
query=[
Query(
nodes_by_id=Query.NodesByID(nodes=[
identifier.Identifier(check=identifier.Check(id="bob"))
]),),
Query(
nodes_in_workplan=identifier.WorkPlan(),
select_checks=Query.SelectChecks(predicates=[
Query.SelectChecks.Predicate(kind='CHECK_KIND_TEST'),
]),
collect_checks=Query.CollectChecks(options=True,),
),
],
version=QueryNodesRequest.VersionRestriction(
require=Revision(ts=Timestamp(seconds=1234, nanos=5678)),),
))
class TestGetOptionsResults(turboci_test_helper.TestBaseClass):
def test_get_option(self):
self.write_nodes(
check(
'a',
kind='CHECK_KIND_ANALYSIS',
options=[_mkStruct({
'hello': [1, 2, 3, 4],
})]))
returned_check = self.read_checks(
'a',
collect=Query.CollectChecks(options=True),
types=list(type_urls(Struct)))[0]
self.assertEqual(
get_option(Struct, returned_check), {
'hello': [1, 2, 3, 4],
})
def test_get_result(self):
self.write_nodes(
check(
'a',
kind='CHECK_KIND_ANALYSIS',
state='CHECK_STATE_WAITING',
results=[_mkStruct({
'hello': [1, 2, 3, 4],
})]))
returned_check = self.read_checks(
'a',
collect=Query.CollectChecks(result_data=True),
types=list(type_urls(Struct)))[0]
self.assertEqual(
get_results(Struct, returned_check), [{
'hello': [1, 2, 3, 4],
}])
if __name__ == '__main__':
test_env.main()