-
Notifications
You must be signed in to change notification settings - Fork 3
58 CI automation of test suite #154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d5d51fe
c99f0c0
2419d8c
98345e3
47869f9
5dfe8b1
ed51b67
4002710
2adabce
fbc8476
21ef1c2
8a38d9a
e707cf8
4298a8e
69f3287
011f0b4
7a9e911
f159208
80a2ea3
6e2a38b
4bf7174
fea0e52
bd78e57
5221b74
ddc889d
a2818d1
2990f2c
7b87fd7
7ed1cc0
98fa005
170726f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| name: adelphi Python | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ 115-breadth-first-testing ] | ||
| pull_request: | ||
| branches: [ 115-breadth-first-testing ] | ||
|
|
||
| jobs: | ||
| build: | ||
|
|
||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v2 | ||
| - name: Set up Python 3.x | ||
| uses: actions/setup-python@v2 | ||
| with: | ||
| python-version: '3.x' | ||
| - name: Install dependencies | ||
| run: | | ||
| cd python | ||
| pip install ./adelphi | ||
| cd adelphi | ||
| pip install -r ./test-requirements.txt | ||
| - name: Execute tests | ||
| run: | | ||
| cd python/adelphi | ||
| test-adelphi | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,6 +56,7 @@ coverage.xml | |
| *.py,cover | ||
| .hypothesis/ | ||
| .pytest_cache/ | ||
| python/adelphi/tox.ini | ||
|
|
||
| # Translations | ||
| *.mo | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,6 @@ | |
|
|
||
| # Functions and constants related to the anonymization process | ||
| from adelphi.store import get_standard_columns_from_table_metadata | ||
| import re | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Randomly discovered unused import, presumably a holdover from previous changes |
||
|
|
||
| # default prefixes for the anonymized names | ||
| KEYSPACE_PREFIX = "ks" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,7 +22,6 @@ | |
|
|
||
| from github import Github | ||
|
|
||
| logging.basicConfig(level=logging.INFO) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This (and store.py below) should definitely not be setting up their own logging... they should be leveraging logs that are configured somewhere else. |
||
| log = logging.getLogger('adelphi') | ||
|
|
||
| # We're assuming any storage repo will be created after the conversion to "main" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,7 +27,8 @@ | |
| from cassandra.cluster import Cluster, ExecutionProfile, EXEC_PROFILE_DEFAULT, default_lbp_factory | ||
| from cassandra.auth import PlainTextAuthProvider | ||
|
|
||
| logging.basicConfig(level=logging.INFO) | ||
| from tenacity import retry | ||
|
|
||
| log = logging.getLogger('adelphi') | ||
|
|
||
| system_keyspaces = set(["system", | ||
|
|
@@ -39,20 +40,26 @@ | |
| "system_views"]) | ||
|
|
||
| def build_auth_provider(username = None,password = None): | ||
| # instantiate auth provider if credentials have been provided | ||
| auth_provider = None | ||
| if username is not None and password is not None: | ||
| auth_provider = PlainTextAuthProvider(username=username, password=password) | ||
| return auth_provider | ||
| """Instantiate auth provider if credentials have been provided""" | ||
| if username is None or password is None: | ||
| return None | ||
| return PlainTextAuthProvider(username=username, password=password) | ||
|
|
||
|
|
||
| @retry | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Annotated to make this a tenacity-aware function but we don't provide global configs here since those are largely context-specific. Individual callers can provide args appropriate for their specific use; see the example in test-adelphi to see what this looks like. |
||
| def with_cluster(cluster_fn, hosts, port, username = None, password = None): | ||
| ep = ExecutionProfile(load_balancing_policy=default_lbp_factory()) | ||
| cluster = Cluster(hosts, port=port, auth_provider=build_auth_provider(username,password), execution_profiles={EXEC_PROFILE_DEFAULT: ep}) | ||
| cluster.connect() | ||
| rv = cluster_fn(cluster) | ||
| cluster.shutdown() | ||
| return rv | ||
| try: | ||
| cluster.connect() | ||
| return cluster_fn(cluster) | ||
| finally: | ||
| cluster.shutdown() | ||
|
|
||
|
|
||
| @retry | ||
| def with_local_cluster(cluster_fn): | ||
| return with_cluster(cluster_fn, ["127.0.0.1"], port=9042) | ||
|
|
||
|
|
||
| def build_keyspace_objects(keyspaces, metadata): | ||
|
|
@@ -71,9 +78,7 @@ def partition(pred, iterable): | |
|
|
||
|
|
||
| def get_standard_columns_from_table_metadata(table_metadata): | ||
| """ | ||
| Return the standard columns and ensure to exclude pk and ck ones. | ||
| """ | ||
| """Return the standard columns and ensure to exclude pk and ck ones""" | ||
| partition_column_names = [c.name for c in table_metadata.partition_key] | ||
| clustering_column_names = [c.name for c in table_metadata.clustering_key] | ||
| standard_columns = [] | ||
|
|
@@ -96,3 +101,28 @@ def set_replication_factor(selected_keyspaces, factor): | |
| log.debug("Replication for keyspace " + ks.name+ ": " + str(ks.replication_strategy)) | ||
| strategy = ks.replication_strategy | ||
| strategy.replication_factor_info = factor | ||
|
|
||
|
|
||
| def create_schema(session, schemaPath): | ||
| """Read schema CQL document and apply CQL commands to cluster""" | ||
| log.info("Creating schema on Cassandra cluster from file {}".format(schemaPath)) | ||
| with open(schemaPath) as schema: | ||
| buff = "" | ||
| for line in schema: | ||
| realLine = line.strip() | ||
| if len(realLine) == 0: | ||
| log.debug("Skipping empty statement") | ||
| continue | ||
| if realLine.startswith("//") or realLine.startswith("--"): | ||
| log.debug("Skipping commented statement") | ||
| continue | ||
| buff += (" " if len(buff) > 0 else "") | ||
| buff += realLine | ||
| if buff.endswith(';'): | ||
| log.debug("Executing statement {}".format(buff)) | ||
| try: | ||
| session.execute(buff) | ||
| except Exception as exc: | ||
| log.error("Exception executing statement: {}".format(buff), exc_info=exc) | ||
| finally: | ||
| buff = "" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| #!python | ||
|
|
||
| # Run test suite for some set of Cassandra versions. | ||
| # | ||
| # We implement this as a front-end script because the tox/pytest/unittest chain | ||
| # isn't great about iterating over a suite of test fixtures and re-running the | ||
| # collected tests for each of them. Rather than fight with the frameworks we | ||
| # manage the fixture iteration manually in this script. This also has the | ||
| # nice side effect of moving a lot of C* checking/session code out of the test | ||
| # suite, which in turn should allow us to write simpler tests. | ||
| import configparser | ||
| import os | ||
| import re | ||
| import sys | ||
|
|
||
| from adelphi.store import with_local_cluster | ||
|
|
||
| import click | ||
| import docker | ||
| from tenacity import retry, stop_after_attempt, wait_fixed | ||
| import tox | ||
|
|
||
|
|
||
| # Default C* versions to include in all integration tests | ||
| DEFAULT_CASSANDRA_VERSIONS = ["2.1.22", "2.2.19", "3.0.23", "3.11.10", "4.0-rc1"] | ||
|
|
||
| TOX_DEPENDENCIES = """pytest | ||
| subprocess32 ~= 3.5 | ||
| tenacity ~= 7.0""" | ||
| TOX_CONFIG = "tox.ini" | ||
|
|
||
|
|
||
| @retry(stop=stop_after_attempt(3), wait=wait_fixed(2)) | ||
| def runCassandraContainer(client, version): | ||
| return client.containers.run(name="adelphi", remove=True, detach=True, ports={9042: 9042}, image="cassandra:{}".format(version)) | ||
|
|
||
|
|
||
| def writeToxIni(version): | ||
| config = configparser.ConfigParser() | ||
| config["tox"] = { "envlist": "py2, py3" } | ||
| config["testenv"] = {"deps": TOX_DEPENDENCIES, \ | ||
| "commands": "pytest {posargs}", \ | ||
| "setenv": "CASSANDRA_VERSION = {}".format(version)} | ||
| with open(TOX_CONFIG, 'w') as configfile: | ||
| config.write(configfile) | ||
|
|
||
|
|
||
| def buildVersionMap(): | ||
| assert sorted(DEFAULT_CASSANDRA_VERSIONS) | ||
| rv = {v:v for v in DEFAULT_CASSANDRA_VERSIONS} | ||
| majorMinorPattern = re.compile(r"(\d\.\d).*") | ||
| for v in DEFAULT_CASSANDRA_VERSIONS: | ||
| majorMinorMatch = majorMinorPattern.match(v) | ||
| if majorMinorMatch: | ||
| majorMinor = majorMinorMatch.group(1) | ||
| rv[majorMinor] = v | ||
| # The reason we needed to asset that our input was sorted; we want the last | ||
| # major version entry we discover in the list to map to the major version. | ||
| # So "2" => "2.2.whatever" rather than "2.1.whatever" | ||
| rv[majorMinor.split('.')[0]] = v | ||
| return rv | ||
|
|
||
|
|
||
| def resolveCassandraVersions(cassandra_versions): | ||
| if not cassandra_versions: | ||
| return DEFAULT_CASSANDRA_VERSIONS | ||
| versionMap = buildVersionMap() | ||
| computedVersions = [x for x in [versionMap.get(v) for v in cassandra_versions] if x is not None] | ||
| if not computedVersions: | ||
| print("Could not compute valid Cassandra versions based on args, using defaults") | ||
| return DEFAULT_CASSANDRA_VERSIONS | ||
| return computedVersions | ||
|
|
||
|
|
||
| @click.command() | ||
| @click.option('--cassandra', '-c', multiple=True, type=str) | ||
| @click.option('--python', '-p', multiple=True, type=click.Choice(["py2","py3"], case_sensitive = False)) | ||
| @click.option("--pytest", "-t", type=str, help="Arguments to be passed to pytest") | ||
| def runtests(cassandra, python, pytest): | ||
| client = docker.from_env() | ||
| tox_args = ["-e {}".format(py) for py in python] if python else [] | ||
| if pytest: | ||
| tox_args.append("--") | ||
| tox_args.append(pytest) | ||
| print("Full tox args: {}".format(tox_args)) | ||
|
|
||
| cassandra_versions = resolveCassandraVersions(cassandra) | ||
| print("Cassandra versions to test: {}".format(','.join(cassandra_versions))) | ||
| exitCodes = [] | ||
| for version in resolveCassandraVersions(cassandra_versions): | ||
|
|
||
| print("Running test suite for Cassandra version {}".format(version)) | ||
| container = runCassandraContainer(client, version) | ||
|
|
||
| print("Validating connection to local Cassandra") | ||
| def validationFn(cluster): | ||
| session = cluster.connect() | ||
| rs = session.execute("select * from system.local") | ||
| print("Connected to Cassandra cluster, first row of system.local: {}".format(rs.one())) | ||
| return (cluster, session) | ||
| with_local_cluster.retry_with(stop=stop_after_attempt(5), wait=wait_fixed(3))(validationFn) | ||
|
|
||
| try: | ||
| if os.path.exists(TOX_CONFIG): | ||
| os.remove(TOX_CONFIG) | ||
| writeToxIni(version) | ||
|
|
||
| # cmdline() will raise SystemExit when it's done so trap that here to avoid | ||
| # exiting all the things | ||
| try: | ||
| tox.cmdline(tox_args) | ||
| except SystemExit as exc: | ||
| exitCodes.append(exc.code) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The exit code that would be returned by tox lives in SystemExit.code. We need to trap this to determine how many of our tox "invocations" resulted in successful tests. |
||
| except Exception as exc: | ||
| print("Exception running tests for Cassandra version {}".format(version), exc) | ||
| finally: | ||
| container.stop() | ||
| sys.exit(sum(1 for x in exitCodes if x != 0)) | ||
|
|
||
| if __name__ == '__main__': | ||
| runtests(obj={}) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| docker ~= 4.4 | ||
| tox ~= 3.22 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A temporary config (since this is branched off of the feature branch changing the integration test framework). This will need to be changed to the default branch before we actually merge it in.