summaryrefslogtreecommitdiff
path: root/buildscripts/antithesis_suite.py
blob: 9c555cb0d4f76a4a97eacdd10d9e4cc0248614ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env python3
"""Command line utility for generating suites for targeting antithesis."""

import os.path
import sys

import click
import yaml

# Get relative imports to work when the package is not installed on the PYTHONPATH.
if __name__ == "__main__" and __package__ is None:
    sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

HOOKS_BLACKLIST = [
    "CleanEveryN",
    "ContinuousStepdown",
    "CheckOrphansDeleted",
    # TODO SERVER-70396 re-enable hook once the checkMetadata feature flag is removed
    # To check the feature flag we need to contact directly the config server that is not exposed in the ExternalFixture
    "CheckMetadataConsistencyInBackground",
]

_SUITES_PATH = os.path.join("buildscripts", "resmokeconfig", "suites")


def delete_archival(suite):
    """Remove archival for Antithesis environment."""
    suite.pop("archive", None)
    suite.get("executor", {}).pop("archive", None)


def make_hooks_compatible(suite):
    """Make hooks compatible in Antithesis environment."""
    if suite.get("executor", {}).get("hooks", None):
        # it's either a list of strings, or a list of dicts, each with key 'class'
        if isinstance(suite["executor"]["hooks"][0], str):
            suite["executor"]["hooks"] = ["AntithesisLogging"] + [
                hook for hook in suite["executor"]["hooks"] if hook not in HOOKS_BLACKLIST
            ]
        elif isinstance(suite["executor"]["hooks"][0], dict):
            suite["executor"]["hooks"] = [{"class": "AntithesisLogging"}] + [
                hook for hook in suite["executor"]["hooks"] if hook["class"] not in HOOKS_BLACKLIST
            ]
        else:
            raise RuntimeError('Unknown structure in hook. File a TIG ticket.')


def use_external_fixture(suite):
    """Use external version of this fixture."""
    if suite.get("executor", {}).get("fixture", None):
        suite["executor"]["fixture"] = {
            "class": f"External{suite['executor']['fixture']['class']}",
            "shell_conn_string": "mongodb://mongos:27017"
        }


def update_test_data(suite):
    """Update TestData to be compatible with antithesis."""
    suite.setdefault("executor", {}).setdefault(
        "config", {}).setdefault("shell_options", {}).setdefault("global_vars", {}).setdefault(
            "TestData", {}).update({"useActionPermittedFile": False})


def update_shell(suite):
    """Update shell for when running in Antithesis."""
    suite.setdefault("executor", {}).setdefault("config", {}).setdefault("shell_options",
                                                                         {}).setdefault("eval", "")
    suite["executor"]["config"]["shell_options"]["eval"] += "jsTestLog = Function.prototype;"


def update_exclude_tags(suite):
    """Update the exclude tags to exclude antithesis incompatible tests."""
    suite.setdefault('selector', {})
    if not suite.get('selector').get('exclude_with_any_tags'):
        suite['selector']['exclude_with_any_tags'] = ["antithesis_incompatible"]
    else:
        suite['selector']['exclude_with_any_tags'].append('antithesis_incompatible')


def make_suite_antithesis_compatible(suite):
    """Modify suite in-place to be antithesis compatible."""
    delete_archival(suite)
    make_hooks_compatible(suite)
    use_external_fixture(suite)
    update_test_data(suite)
    update_shell(suite)
    update_exclude_tags(suite)


@click.group()
def cli():
    """CLI Entry point."""
    pass


def _generate(suite_name: str) -> None:
    with open(os.path.join(_SUITES_PATH, f"{suite_name}.yml")) as fstream:
        suite = yaml.safe_load(fstream)

    make_suite_antithesis_compatible(suite)

    out = yaml.dump(suite)
    with open(os.path.join(_SUITES_PATH, f"antithesis_{suite_name}.yml"), "w") as fstream:
        fstream.write(
            "# this file was generated by buildscripts/antithesis_suite.py generate {}\n".format(
                suite_name))
        fstream.write("# Do not modify by hand\n")
        fstream.write(out)


@cli.command()
@click.argument('suite_name')
def generate(suite_name: str) -> None:
    """Generate a single suite."""
    _generate(suite_name)


@cli.command('generate-all')
def generate_all():
    """Generate all suites."""
    for path in os.listdir(_SUITES_PATH):
        if os.path.isfile(os.path.join(_SUITES_PATH, path)):
            suite = path.split(".")[0]
            _generate(suite)


if __name__ == "__main__":
    cli()