blob: e6343119fed3334365d0e4e9c7311a786abeaf01 [file]
# Copyright 2025 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Full test of limited_cq."""
from __future__ import annotations
from collections.abc import Iterator
from recipe_engine import post_process, recipe_api, recipe_test_api
from PB.go.chromium.org.luci.buildbucket.proto import common as common_pb
from PB.recipe_modules.pigweed.limited_cq.tests.full import InputProperties
from RECIPE_MODULES.pigweed.checkout import api as checkout_api
DEPS = [
'pigweed/checkout',
'pigweed/limited_cq',
'recipe_engine/buildbucket',
'recipe_engine/cv',
'recipe_engine/properties',
'recipe_engine/step',
]
INLINE_PROPERTIES_PROTO = """
import "recipe_modules/pigweed/checkout/options.proto";
message InputProperties {
recipe_modules.pigweed.checkout.Options checkout_options = 1;
}
"""
PROPERTIES = InputProperties
def RunSteps(
api: recipe_api.RecipeApi,
props: InputProperties,
) -> None:
checkout = api.checkout(options=props.checkout_options)
res = api.limited_cq.exit_early_if_not_approved(
checkout.changes,
verbose=True,
)
if res:
with api.step.nest('status'):
status = {
common_pb.SUCCESS: 'SUCCESS',
common_pb.FAILURE: 'FAILURE',
common_pb.INFRA_FAILURE: 'INFRA_FAILURE',
}[res.status]
api.step.empty(str(status))
def GenTests(
api: recipe_test_api.RecipeTestApi,
) -> Iterator[recipe_test_api.TestData]:
def assert_public_project():
return api.post_check(
post_process.MustRun,
'checking change approvals.project is public',
)
def assert_not_tryjob():
return api.post_check(
post_process.MustRun,
'checking change approvals.not a tryjob',
)
def assert_continue(reason=None):
reason_part = rf'\.{reason}' if reason else ''
return (
api.post_check(
post_process.MustRunRE,
rf'checking change approvals.*continue{reason_part}',
)
+ api.post_check(
post_process.DoesNotRun,
'checking change approvals.exit early',
)
+ api.post_check(
post_process.DoesNotRun,
'checking change approvals.exit early',
)
+ api.post_check(
post_process.PropertiesDoNotContain,
'$recipe_engine/cv/output',
)
)
def assert_exit_early():
def validate_reuse_properties(check, prop):
if check('reusability' in prop):
if check('modeAllowlist' in prop['reusability']):
check(
'NEW_PATCHSET_RUN'
in prop['reusability']['modeAllowlist'],
)
check(len(prop['reusability']['modeAllowlist']) == 1)
return True
return (
api.post_check(
post_process.MustRun,
'checking change approvals.exit early',
)
+ api.post_check(
post_process.DoesNotRunRE,
r'checking change approvals\..*\.continue',
)
+ api.post_process(
post_process.PropertyMatchesCallable,
'$recipe_engine/cv/output',
validate_reuse_properties,
)
)
def assert_status(status: str):
return api.post_check(post_process.MustRun, f'status.{status}')
def drop_expectations_must_be_last():
# No need for expectation files, everything of note here is tested by
# assertions. This must be the last thing added to the test.
return api.post_process(post_process.DropExpectation)
def defaults(
project='pigweed-internal',
cv_active=True,
run_mode=api.cv.DRY_RUN,
bb_props=api.buildbucket.try_build,
tags=(),
created_by=None,
):
bb_kwargs = {}
if created_by:
bb_kwargs['created_by'] = created_by
res = bb_props(
project=project,
git_repo=checkout_api.PIGWEED_REMOTE,
tags=tags,
**bb_kwargs,
)
if cv_active:
res += api.cv(run_mode=run_mode)
res += api.properties(checkout_options=api.checkout.git_options())
return res
yield api.test(
'skipped-project',
defaults(project='pigweed'),
api.cv(run_mode=api.cv.DRY_RUN),
assert_public_project(),
drop_expectations_must_be_last(),
)
yield api.test(
'skipped-ci',
defaults(bb_props=api.buildbucket.ci_build),
api.cv(run_mode=api.cv.DRY_RUN),
api.properties(checkout_options=api.checkout.git_options()),
assert_not_tryjob(),
drop_expectations_must_be_last(),
)
yield api.test(
'approved-owners',
defaults(),
api.checkout.cl_branch_parents(
submit_requirements={
'Code-Owners': 'SATISFIED',
'Code-Review': 'ERROR',
'Review-Enforcement': 'UNSATISFIED',
},
),
assert_continue('Code-Owners'),
drop_expectations_must_be_last(),
)
yield api.test(
'approved-code-review',
defaults(),
api.checkout.cl_branch_parents(
submit_requirements={
'Code-Owners': 'ERROR',
'Code-Review': 'OVERRIDDEN',
'Review-Enforcement': 'UNSATISFIED',
},
),
assert_continue('Code-Review'),
drop_expectations_must_be_last(),
)
yield api.test(
'approved-review-enforcement',
defaults(),
api.checkout.cl_branch_parents(
submit_requirements={
'Code-Owners': 'ERROR',
'Code-Review': 'UNSATISFIED',
'Review-Enforcement': 'SATISFIED',
},
),
assert_continue('Review-Enforcement'),
drop_expectations_must_be_last(),
)
yield api.test(
'approved-uploader',
defaults(),
api.checkout.cl_branch_parents(uploader='noreply@google.com'),
assert_continue(),
drop_expectations_must_be_last(),
)
yield api.test(
'untrusted-uploader-dry-run',
defaults(),
api.checkout.cl_branch_parents(uploader='noreply@example.com'),
assert_exit_early(),
assert_status('SUCCESS'),
drop_expectations_must_be_last(),
)
yield api.test(
'untrusted-uploader-no-cv',
defaults(cv_active=False),
api.checkout.cl_branch_parents(uploader='noreply@example.com'),
assert_exit_early(),
assert_status('FAILURE'),
drop_expectations_must_be_last(),
)
yield api.test(
'untrusted-uploader-full_run',
defaults(run_mode=api.cv.FULL_RUN),
api.checkout.cl_branch_parents(uploader='noreply@example.com'),
assert_exit_early(),
assert_status('FAILURE'),
drop_expectations_must_be_last(),
)
def pair(key: str, value: str):
return common_pb.StringPair(key=key, value=value)
yield api.test(
'approved-triggerer',
defaults(tags=[pair('cq_triggerer', 'noreply@google.com')]),
api.checkout.cl_branch_parents(uploader='noreply@example.com'),
assert_continue('triggerer'),
drop_expectations_must_be_last(),
)
yield api.test(
'untrusted-triggerer',
defaults(tags=[pair('cq_triggerer', 'noreply@example.com')]),
api.checkout.cl_branch_parents(uploader='noreply@example.com'),
assert_exit_early(),
drop_expectations_must_be_last(),
)
yield api.test(
'approved-created_by',
defaults(created_by='user:noreply@google.com'),
api.checkout.cl_branch_parents(uploader='noreply@example.com'),
assert_continue('created by'),
drop_expectations_must_be_last(),
)
yield api.test(
'untrusted-created_by',
defaults(created_by='user:noreply@example.com'),
api.checkout.cl_branch_parents(uploader='noreply@example.com'),
assert_exit_early(),
drop_expectations_must_be_last(),
)