blob: 1d135133b03126379521e084c1aa26e513e37a39 [file] [log] [blame]
# Copyright 2021 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
# 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.
"""Check that lines were not removed from the token database."""
import re
from import InputProperties
from recipe_engine import post_process
DEPS = [
PROPERTIES = InputProperties
def _read_tokens(api, path, revision):
"""Read token hashes from a given path at a specific revision."""
step = api.git(
f'show {revision}',
step.presentation.logs['stdout'] = step.stdout
token_lines = step.stdout.strip().split(b'\n')
return frozenset(x.split(b',')[0] for x in token_lines if x.strip())
def RunSteps(api, props):
if not props.tokendb_paths:
raise api.step.StepFailure('no tokendb_paths property')
# TODO(mohrr) Remove nesting. Apparently the gerrit module doesn't work as
# a top-level step. Change the next line to 'if True:' to reproduce.
with api.step.nest('gerrit'):
res = api.util.get_change_with_comments()
match = api.util.find_matching_comment(
re.compile(r'Token-Database-Removal-Reason: \w.*'), res.comments,
if match:
checkout = api.checkout(props.checkout_options)
with api.context(cwd=checkout.root):
kwargs = {
'stdout': api.raw_io.output_text(),
'step_test_data': lambda: api.raw_io.test_api.stream_output_text(
step = api.git(
'git show --numstat',
step.presentation.logs["stdout"] = step.stdout
lines = step.stdout.strip().split('\n')
# Each line of output looks like this:
for line in lines:
if not line:
_, removed, path = line.split()
with api.step.nest(str(path)):
if not int(removed):
prev_tokens = _read_tokens(api, path, 'HEAD~1')
curr_tokens = _read_tokens(api, path, 'HEAD')
if not prev_tokens.issubset(curr_tokens):
with api.step.nest('removed tokens') as pres:
pres.step_summary_text = ', '.join(
x.decode() for x in prev_tokens - curr_tokens
raise api.step.StepFailure(
'Lines are not allowed to be removed from token '
"database {}. If there's a good reason to remove them "
'post a Gerrit comment explaining why that starts with '
'"Token-Database-Removal-Reason: ".'.format(path)
def GenTests(api):
def diff(path, added, removed):
return f'{added} {removed} {path}'
def tokens(path, old_contents, new_contents):
return api.step_data(
f'{path}.show HEAD~1', stdout=api.raw_io.output(old_contents)
) + api.step_data(
f'{path}.show HEAD', stdout=api.raw_io.output(new_contents)
def test(name, paths=(), diffs=None, status='success', comment=None):
res = api.status_check.test(name, status=status)
res +=
res += api.checkout.try_test_data()
res +=**api.checkout.git_properties())
if diffs is not None:
commit_summary = '\n'.join(diffs)
res += api.step_data(
'git show --numstat',
if comment:
res += api.util.change_comment(comment)
return res
yield test('no-props', status='failure')
yield test('no-change', paths=['token.db'], diffs=[])
yield test('addition', paths=['token.db'], diffs=[diff('token.db', 1, 0)])
yield (
diffs=[diff('token.db', 0, 1)],
+ tokens('token.db', b'1234,\n2345,\n', b'2345,\n')
yield test(
comment='Token-Database-Removal-Reason: because',
yield (
test('update', paths=['token.db'], diffs=[diff('token.db', 1, 1)])
+ tokens('token.db', b'1234,old\n2345,foo\n', b'1234,new\n2345,foo\n')