sapling/tests/integration/integration_runner.py
Anastasiya Zhyrkevich 3919c0eb4c LFS verification tool
Summary:
Purpose:
- Sha256 alias link to file_content is a required part of LFS getfiles works correct.
LFS protocol uses SHA-256 to refer to the file content. Mononoke uses Blake2.
To support LFS in Mononoke we need to set up a link from SHA-256 hash of the content to blake2 of the content.
These links are called aliases.

- Aliases are uploading together with file content blobs.
But only for new push operations.
- If repo is blobimported from somewhere, we need to make sure, that all the links are in blobstore.
If repo was blobimported before aliases were added then it may miss aliases for some blobs.
- This tool can be used to
   - find if any aliases are missing
   - fill missing aliases.

Implementation:
- Run in repo.
- Iterate through all changesets.
- Go through all the file_content blobs in the changesets
- Verify/generate alias256 links to file_content blobs.

Mode supported:
- verify, count the number of errors and print to console
- generate, if blob is missing to add it to the blobstore

Reviewed By: StanislavGlebik

Differential Revision: D10461827

fbshipit-source-id: c2673c139e2f2991081c4024db7b85953d2c5e35
2018-11-08 03:41:40 -08:00

172 lines
5.4 KiB
Python

#!/usr/bin/env python3
# Copyright (c) 2004-present, Facebook, Inc.
# All Rights Reserved.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""Runner for Mononoke/Mercurial integration tests."""
import contextlib
import multiprocessing
import os
import shutil
import sys
import tempfile
import xml.etree.ElementTree as ET
import click
from libfb.py import parutil, pathutils
from .third_party import hg_run_tests
TESTDIR_PATH = 'scm/mononoke/tests/integration'
MONONOKE_BLOBIMPORT_TARGET = '//scm/mononoke:blobimport'
MONONOKE_ALIAS_VERIFY_TARGET = '//scm/mononoke:aliasverify'
MONONOKE_BONSAI_VERIFY_TARGET = '//scm/mononoke:bonsai_verify'
MONONOKE_APISERVER_TARGET = '//scm/mononoke/apiserver:apiserver'
DUMMYSSH_TARGET = '//scm/mononoke/tests/integration:dummyssh'
BINARY_HG_TARGET = '//scm/hg:hg'
MONONOKE_HGCLI_TARGET = '//scm/mononoke/hgcli:hgcli'
MONONOKE_SERVER_TARGET = '//scm/mononoke:mononoke'
@click.command()
@click.option('--dry-run', default=False, is_flag=True, help='list tests')
@click.option(
'--interactive',
default=False,
is_flag=True,
help='prompt to accept changed output'
)
@click.option('--output', default='', help='output directory')
@click.option(
'--verbose', default=False, is_flag=True, help='output verbose messages'
)
@click.option(
'--debug',
default=False,
is_flag=True,
help='debug mode: write output of test scripts to console rather than '
'capturing and diffing it (disables timeout)'
)
@click.option(
'--keep-tmpdir',
default=False,
is_flag=True,
help='keep temporary directory after running tests'
)
@click.option(
'--simple-test-selector',
default=None,
help='select an individual test to run',
)
@click.argument(
'tests',
nargs=-1,
type=click.Path(),
)
@click.pass_context
def run(
ctx, tests, dry_run, interactive, output, verbose, debug,
simple_test_selector, keep_tmpdir
):
runner = hg_run_tests.TestRunner()
testdir = parutil.get_dir_path(TESTDIR_PATH)
# Also add to the system path because the Mercurial run-tests.py does an
# absolute import of killdaemons etc.
sys.path.insert(0, os.path.join(testdir, 'third_party'))
# Use hg.real to avoid going through the wrapper and incurring slowdown
# from subprocesses.
# XXX is this the right thing to do?
args = ['--with-hg', get_hg_binary()]
if dry_run:
args.append('--list-tests')
if interactive:
args.append('-i')
if verbose:
args.append('--verbose')
if debug:
args.append('--debug')
if keep_tmpdir:
args.append('--keep-tmpdir')
args.extend(['-j', '%d' % multiprocessing.cpu_count()])
if simple_test_selector is not None:
suite, test = simple_test_selector.split(',', 1)
if suite != 'run-tests':
raise click.BadParameter(
'suite should always be "run-tests"', ctx,
param_hint='simple_test_selector'
)
args.append(test)
if tests:
args.extend(tests)
# In --dry-run mode, the xunit output has to be written to stdout.
# In regular (run-tests) mode, the output has to be written to the specified
# output directory.
if output == '':
output = None
_fp, xunit_output = tempfile.mkstemp(dir=output)
add_to_environ('MONONOKE_BLOBIMPORT', MONONOKE_BLOBIMPORT_TARGET)
add_to_environ('MONONOKE_ALIAS_VERIFY', MONONOKE_ALIAS_VERIFY_TARGET)
add_to_environ('MONONOKE_BONSAI_VERIFY', MONONOKE_BONSAI_VERIFY_TARGET)
add_to_environ(
'DUMMYSSH', DUMMYSSH_TARGET, pathutils.BuildRuleTypes.PYTHON_BINARY
)
add_to_environ('MONONOKE_APISERVER', MONONOKE_APISERVER_TARGET)
add_to_environ('MONONOKE_HGCLI', MONONOKE_HGCLI_TARGET)
add_to_environ('MONONOKE_SERVER', MONONOKE_SERVER_TARGET)
# Provide an output directory so that we don't write to a xar's read-only
# filesystem.
output_dir = tempfile.mkdtemp()
try:
args.extend(['--xunit', xunit_output, '--outputdir', output_dir])
with contextlib.redirect_stdout(sys.stderr):
# Do this here to influence as little code as possible -- in
# particular, add_to_environ depends on getcwd always being inside
# fbcode
os.chdir(testdir)
ret = runner.run(args)
if dry_run:
# The output must go to stdout. Set simple_test_selector to make
# execution runs simpler.
with open(xunit_output, 'rb') as f:
xunit_xml = ET.parse(f)
xunit_xml.getroot().set(
'runner_capabilities', 'simple_test_selector'
)
xunit_xml.write(sys.stdout.buffer, xml_declaration=True)
ctx.exit(ret)
finally:
try:
# If an output was specified, xunit_output is owned by the caller
# and is the caller's responsibility to clean up.
if output is None:
os.unlink(xunit_output)
except OSError:
pass
shutil.rmtree(output_dir, ignore_errors=True)
def add_to_environ(var, target, rule_type=pathutils.BuildRuleTypes.RUST_BINARY):
os.environ[var] = pathutils.get_build_rule_output_path(target, rule_type)
def get_hg_binary():
return pathutils.get_build_rule_output_path(
BINARY_HG_TARGET, pathutils.BuildRuleTypes.PYTHON_BINARY
)
if __name__ == '__main__':
run()