Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d5d51fe
First sketch of an idea, still lots to flesh out yet
Jun 9, 2021
c99f0c0
Add automated generation of tox.ini
Jun 9, 2021
2419d8c
Single file nb tests now working
Jun 9, 2021
98345e3
Forgot to clean out tests.integration.nb package init
Jun 9, 2021
47869f9
First cut at a consolidated test_cql.py
Jun 9, 2021
5dfe8b1
Tests appear to be passing on py2 and py3 again
Jun 9, 2021
ed51b67
Now that export-specific functionality is consolidated within a singl…
Jun 9, 2021
4002710
Would like to keep unittests runnable on their own as well
Jun 9, 2021
2adabce
Fix tox invocation to avoid exiting after first run completes
Jun 9, 2021
fbc8476
test_nb doesn't need method-specific data for init so let's make sche…
Jun 9, 2021
21ef1c2
Add click for handling CLI args
Jun 10, 2021
8a38d9a
Comment updates
Jun 10, 2021
e707cf8
Rename + making test app executable
Jun 10, 2021
4298a8e
Update keyspace drop logic to be more automatic
Jun 10, 2021
69f3287
Forgot to add tox to the test dependencies
Jun 11, 2021
011f0b4
Just testing some things out
Jun 11, 2021
7a9e911
Try specifying a branch
Jun 11, 2021
f159208
Moved tests.util.cassandra_util into adelphi.store so that necessary …
Jun 14, 2021
80a2ea3
Promote test-adelphi to a full bin script within the package; should …
Jun 14, 2021
6e2a38b
Forgot to include pip install of test dependencies as well
Jun 14, 2021
4bf7174
Make sure we're in the directory with setup.py when we invoke test-ad…
Jun 14, 2021
fea0e52
Explicit step for running tests + removal of constraints around C*/Py…
Jun 15, 2021
bd78e57
Separate cd for the separate command
Jun 15, 2021
5221b74
Breaking 2.1 CQL integration tests to confirm that GH actions tests c…
Jun 15, 2021
ddc889d
Fix CLI logic to handle specification of partial C* version numbers
Jun 16, 2021
a2818d1
Set test-adelphi exit status code based on the number of tox invocati…
Jun 16, 2021
2990f2c
Revert "Breaking 2.1 CQL integration tests to confirm that GH actions…
Jun 16, 2021
7b87fd7
Removing original (and incorrect) code to get tox exit codes
Jun 16, 2021
7ed1cc0
Update .gitignore to exclude generated tox file (#156)
jdonenine Jun 17, 2021
98fa005
Bump 3.11.x C* version to 3.11.10
Jun 21, 2021
170726f
Merge branch '115-breadth-first-testing' into 58-theyre-workflows-so-…
Jun 21, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/adelphi-python.yaml
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 ]
Copy link
Collaborator Author

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.


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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
python/adelphi/tox.ini

# Translations
*.mo
Expand Down
1 change: 0 additions & 1 deletion python/adelphi/adelphi/anonymize.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

# Functions and constants related to the anonymization process
from adelphi.store import get_standard_columns_from_table_metadata
import re
Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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"
Expand Down
1 change: 0 additions & 1 deletion python/adelphi/adelphi/gh.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

from github import Github

logging.basicConfig(level=logging.INFO)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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"
Expand Down
56 changes: 43 additions & 13 deletions python/adelphi/adelphi/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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):
Expand All @@ -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 = []
Expand All @@ -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 = ""
121 changes: 121 additions & 0 deletions python/adelphi/bin/test-adelphi
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)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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={})
5 changes: 3 additions & 2 deletions python/adelphi/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
'cassandra-driver ~= 3.24',
'click ~= 7.1',
'PyGithub ~= 1.45',
'PyYAML ~= 5.4'
'PyYAML ~= 5.4',
'tenacity ~= 7.0'
]

if not PY3:
Expand Down Expand Up @@ -48,6 +49,6 @@
'Topic :: Software Development :: Libraries :: Python Modules'
],
packages=['adelphi'],
scripts=['bin/adelphi'],
scripts=['bin/adelphi','bin/test-adelphi'],
install_requires=dependencies,
)
2 changes: 2 additions & 0 deletions python/adelphi/test-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
docker ~= 4.4
tox ~= 3.22
Loading