summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Bradford <david.bradford@mongodb.com>2018-12-07 13:24:43 -0500
committerDavid Bradford <david.bradford@mongodb.com>2018-12-07 13:24:43 -0500
commitbaf4fa098e091da7107b33dce15a254cfcc44a26 (patch)
tree231485de14265a6036be536bc374cdbef15e5e23
parentae54786e2830073387673b4c0ea921559065ed5f (diff)
downloadmongo-baf4fa098e091da7107b33dce15a254cfcc44a26.tar.gz
SERVER-38110 SERVER-38113: Generate resmoke config and evg config for a sub-suite
-rw-r--r--.gitignore1
-rwxr-xr-xbuildscripts/evergreen_gen_fuzzer_tests.py83
-rwxr-xr-xbuildscripts/generate_resmoke_suites.py228
-rw-r--r--buildscripts/resmokeconfig/suites/buildscripts_test.yml1
-rw-r--r--buildscripts/templates/generate_resmoke_suites/replica_sets_auth.yml.j250
-rw-r--r--buildscripts/templates/generate_resmoke_suites/replica_sets_ese.yml.j242
-rw-r--r--buildscripts/templates/generate_resmoke_suites/sharding.yml.j234
-rw-r--r--buildscripts/templates/generate_resmoke_suites/sharding_auth.yml.j271
-rw-r--r--buildscripts/templates/generate_resmoke_suites/sharding_auth_audit.yml.j273
-rw-r--r--buildscripts/templates/generate_resmoke_suites/sharding_ese.yml.j243
-rw-r--r--buildscripts/templates/generate_resmoke_suites/sharding_last_stable_mongos_and_mixed_shards.yml.j297
-rw-r--r--buildscripts/tests/test_evergreen_gen_fuzzer_tests.py36
-rw-r--r--buildscripts/tests/test_generate_resmoke_suites.py213
-rw-r--r--buildscripts/tests/util/test_read_config.py38
-rw-r--r--buildscripts/tests/util/test_taskname.py17
-rw-r--r--buildscripts/util/read_config.py44
-rw-r--r--buildscripts/util/taskname.py20
-rw-r--r--etc/evergreen.yml2
-rw-r--r--etc/pip/components/resmoke.req1
-rw-r--r--etc/pip/constraints.txt23
20 files changed, 477 insertions, 640 deletions
diff --git a/.gitignore b/.gitignore
index efb56426b11..a1564ef2888 100644
--- a/.gitignore
+++ b/.gitignore
@@ -169,3 +169,4 @@ src/mongo/embedded/java/jar/build/
local.properties
compile_commands.json
+generated_resmoke_config
diff --git a/buildscripts/evergreen_gen_fuzzer_tests.py b/buildscripts/evergreen_gen_fuzzer_tests.py
index c22df9c972d..161802fa4e4 100755
--- a/buildscripts/evergreen_gen_fuzzer_tests.py
+++ b/buildscripts/evergreen_gen_fuzzer_tests.py
@@ -9,14 +9,15 @@ import os
from collections import namedtuple
-import yaml
-
from shrub.config import Configuration
from shrub.command import CommandDefinition
from shrub.task import TaskDependency
from shrub.variant import DisplayTaskDefinition
from shrub.variant import TaskSpec
+import util.read_config as read_config
+import util.taskname as taskname
+
CONFIG_DIRECTORY = "generated_resmoke_config"
ConfigOptions = namedtuple("ConfigOptions", [
@@ -35,34 +36,6 @@ ConfigOptions = namedtuple("ConfigOptions", [
])
-def _get_config_value(attrib, cmd_line_options, config_file_data, required=False, default=None):
- """
- Get the configuration value to use.
-
- First use command line options, then config file option, then the default. If required is
- true, throw an exception if the value is not found.
-
- :param attrib: Attribute to search for.
- :param cmd_line_options: Command line options.
- :param config_file_data: Config file data.
- :param required: Is this option required.
- :param default: Default value if option is not found.
- :return: value to use for this option.
- """
- value = getattr(cmd_line_options, attrib, None)
- if value:
- return value
-
- value = config_file_data.get(attrib)
- if value:
- return value
-
- if required:
- raise ValueError("{0} must be specified".format(attrib))
-
- return default
-
-
def _get_config_options(cmd_line_options, config_file): # pylint: disable=too-many-locals
"""
Get the configuration to use.
@@ -73,32 +46,33 @@ def _get_config_options(cmd_line_options, config_file): # pylint: disable=too-m
:param config_file: config file to use.
:return: ConfigOptions to use.
"""
- config_file_data = {}
- if config_file:
- with open(config_file) as file_handle:
- config_file_data = yaml.load(file_handle)
+ config_file_data = read_config.read_config_file(config_file)
num_files = int(
- _get_config_value("num_files", cmd_line_options, config_file_data, required=True))
+ read_config.get_config_value("num_files", cmd_line_options, config_file_data,
+ required=True))
num_tasks = int(
- _get_config_value("num_tasks", cmd_line_options, config_file_data, required=True))
- resmoke_args = _get_config_value("resmoke_args", cmd_line_options, config_file_data, default="")
- npm_command = _get_config_value("npm_command", cmd_line_options, config_file_data,
- default="jstestfuzz")
- jstestfuzz_vars = _get_config_value("jstestfuzz_vars", cmd_line_options, config_file_data,
- default="")
- name = _get_config_value("name", cmd_line_options, config_file_data, required=True)
- variant = _get_config_value("build_variant", cmd_line_options, config_file_data, required=True)
- continue_on_failure = _get_config_value("continue_on_failure", cmd_line_options,
- config_file_data, default="false")
- resmoke_jobs_max = _get_config_value("resmoke_jobs_max", cmd_line_options, config_file_data,
- default="0")
- should_shuffle = _get_config_value("should_shuffle", cmd_line_options, config_file_data,
- default="false")
- timeout_secs = _get_config_value("timeout_secs", cmd_line_options, config_file_data,
- default="1800")
- use_multiversion = _get_config_value("task_path_suffix", cmd_line_options, config_file_data,
- default=False)
+ read_config.get_config_value("num_tasks", cmd_line_options, config_file_data,
+ required=True))
+ resmoke_args = read_config.get_config_value("resmoke_args", cmd_line_options, config_file_data,
+ default="")
+ npm_command = read_config.get_config_value("npm_command", cmd_line_options, config_file_data,
+ default="jstestfuzz")
+ jstestfuzz_vars = read_config.get_config_value("jstestfuzz_vars", cmd_line_options,
+ config_file_data, default="")
+ name = read_config.get_config_value("name", cmd_line_options, config_file_data, required=True)
+ variant = read_config.get_config_value("build_variant", cmd_line_options, config_file_data,
+ required=True)
+ continue_on_failure = read_config.get_config_value("continue_on_failure", cmd_line_options,
+ config_file_data, default="false")
+ resmoke_jobs_max = read_config.get_config_value("resmoke_jobs_max", cmd_line_options,
+ config_file_data, default="0")
+ should_shuffle = read_config.get_config_value("should_shuffle", cmd_line_options,
+ config_file_data, default="false")
+ timeout_secs = read_config.get_config_value("timeout_secs", cmd_line_options, config_file_data,
+ default="1800")
+ use_multiversion = read_config.get_config_value("task_path_suffix", cmd_line_options,
+ config_file_data, default=False)
return ConfigOptions(num_files, num_tasks, resmoke_args, npm_command, jstestfuzz_vars, name,
variant, continue_on_failure, resmoke_jobs_max, should_shuffle,
@@ -131,7 +105,8 @@ def _generate_evg_tasks(options):
task_specs = []
for task_index in range(options.num_tasks):
- name = _name_task(options.name, task_index, options.num_tasks) + "_" + options.variant
+ name = taskname.name_generated_task(options.name, task_index, options.num_tasks,
+ options.variant)
task_names.append(name)
task_specs.append(TaskSpec(name))
task = evg_config.task(name)
diff --git a/buildscripts/generate_resmoke_suites.py b/buildscripts/generate_resmoke_suites.py
index 6268de9f258..dba1922a584 100755
--- a/buildscripts/generate_resmoke_suites.py
+++ b/buildscripts/generate_resmoke_suites.py
@@ -14,29 +14,49 @@ import itertools
import logging
import os
import sys
-
from collections import defaultdict
from collections import namedtuple
from operator import itemgetter
-from jinja2 import Template
+import yaml
+
+from shrub.config import Configuration
+from shrub.command import CommandDefinition
+from shrub.task import TaskDependency
+from shrub.variant import DisplayTaskDefinition
+from shrub.variant import TaskSpec
from client.github import GithubApi
import client.evergreen as evergreen
+import util.read_config as read_config
+import util.taskname as taskname
import util.testname as testname
import util.time as timeutil
LOGGER = logging.getLogger(__name__)
-TEMPLATES_DIR = "buildscripts/templates/generate_resmoke_suites"
-TEST_SUITE_DIR = "buildscripts/resmokeconfig/suites"
-
MAX_RUNTIME_KEY = "max_runtime"
+TEST_SUITE_DIR = os.path.join("buildscripts", "resmokeconfig", "suites")
+CONFIG_DIR = "generated_resmoke_config"
+
+HEADER_TEMPLATE = """# DO NOT EDIT THIS FILE. All manual edits will be lost.
+# This file was generated by {file} from
+# {suite_file}.
+"""
CommitRange = namedtuple("CommitRange", ["start", "end"])
ProjectTarget = namedtuple("ProjectTarget", ["owner", "project", "branch"])
Dependencies = namedtuple("Dependencies", ["evergreen", "github"])
+ConfigOptions = namedtuple("ConfigOptions", [
+ "max_sub_suites",
+ "resmoke_args",
+ "resmoke_jobs_max",
+ "run_multiple_jobs",
+ "suite",
+ "task",
+ "variant",
+])
def enable_logging():
@@ -49,6 +69,35 @@ def enable_logging():
)
+def get_config_options(cmd_line_options, config_file):
+ """
+ Get the configuration to use for generated tests.
+
+ Command line options override config file options.
+
+ :param cmd_line_options: Command line options specified.
+ :param config_file: config file to use.
+ :return: ConfigOptions to use.
+ """
+ config_file_data = read_config.read_config_file(config_file)
+
+ max_sub_suites = read_config.get_config_value("max_sub_suites", cmd_line_options,
+ config_file_data)
+ resmoke_args = read_config.get_config_value("resmoke_args", cmd_line_options, config_file_data,
+ default="")
+ resmoke_jobs_max = read_config.get_config_value("resmoke_jobs_max", cmd_line_options,
+ config_file_data)
+ run_multiple_jobs = read_config.get_config_value("run_multiple_jobs", cmd_line_options,
+ config_file_data, default="true")
+ task = read_config.get_config_value("task", cmd_line_options, config_file_data, required=True)
+ suite = read_config.get_config_value("suite", cmd_line_options, config_file_data, default=task)
+ variant = read_config.get_config_value("build_variant", cmd_line_options, config_file_data,
+ required=True)
+
+ return ConfigOptions(max_sub_suites, resmoke_args, resmoke_jobs_max, run_multiple_jobs, suite,
+ task, variant)
+
+
def get_start_and_end_commit_since_date(github_api, target, start_date):
"""Get the first and last commits on the given branch from the start date specified."""
@@ -247,32 +296,88 @@ def divide_tests_into_suites_by_maxtime(tests, sorted_tests, max_time_seconds, m
return suites
-def get_misc_model(test_list, extra_model_data=None):
- """Build a model that will run any missing tests."""
- model = {
- "is_misc": True,
- "excluded_tests": test_list,
- }
+def generate_subsuite_file(source_suite_name, target_suite_name, roots=None, excludes=None):
+ """
+ Read and evaluate the yaml suite file.
+
+ Override selector.roots and selector.excludes with the provided values. Write the results to
+ target_suite_name.
+ """
+ source_file = os.path.join(TEST_SUITE_DIR, source_suite_name + ".yml")
+ with open(source_file, "r") as fstream:
+ suite_config = yaml.load(fstream)
+
+ with open(os.path.join(CONFIG_DIR, target_suite_name + ".yml"), 'w') as out:
+ out.write(HEADER_TEMPLATE.format(file=__file__, suite_file=source_file))
+ if roots:
+ suite_config['selector']['roots'] = roots
+ if excludes:
+ suite_config['selector']['exclude_files'] = excludes
+ out.write(yaml.dump(suite_config, default_flow_style=False, Dumper=yaml.SafeDumper))
+
+
+def render_suite(suites, suite_name):
+ """Render the given suites into yml files that can be used by resmoke.py."""
+ for idx, suite in enumerate(suites):
+ suite.name = taskname.name_generated_task(suite_name, idx, len(suites))
+ generate_subsuite_file(suite_name, suite.name, roots=suite.tests)
+
- if extra_model_data:
- model.update(extra_model_data)
+def render_misc_suite(test_list, suite_name):
+ """Render a misc suite to run any tests that might be added to the directory."""
+ subsuite_name = "{0}_{1}".format(suite_name, "misc")
+ generate_subsuite_file(suite_name, subsuite_name, excludes=test_list)
- return model
+def prepare_directory_for_suite(directory):
+ """Ensure that dir exists."""
+ if not os.path.exists(directory):
+ os.makedirs(directory)
-def render_template(model, task, index):
- """Render the specified model as a yml file in the test suites directory."""
- template_file = "{dir}/{task}.yml.j2".format(dir=TEMPLATES_DIR, task=task)
- target_file = "{dir}/{task}_{index}.yml".format(dir=TEST_SUITE_DIR, task=task, index=index)
- render(model, template_file, target_file)
+def generate_evg_config(suites, options):
+ """Generate evergreen configuration for the given suites."""
+ evg_config = Configuration()
+ task_names = []
+ task_specs = []
-def render(model, source, destination):
- """Render the specified model with the template at `source` to the file `destination`."""
- with open(source, "r") as inp, open(destination, "w") as out:
- template = Template(inp.read(), trim_blocks=True)
- out.write(template.render(model))
+ def generate_task(sub_suite_name, sub_task_name):
+ """Generate evergreen config for a resmoke task."""
+ task_names.append(sub_task_name)
+ task_specs.append(TaskSpec(sub_task_name))
+ task = evg_config.task(sub_task_name)
+
+ target_suite_file = os.path.join(CONFIG_DIR, sub_suite_name)
+
+ run_tests_vars = {
+ "resmoke_args": "--suites={0} {1}".format(target_suite_file, options.resmoke_args),
+ "run_multiple_jobs": options.run_multiple_jobs,
+ }
+
+ if options.resmoke_jobs_max:
+ run_tests_vars["resmoke_jobs_max"] = options.resmoke_jobs_max
+
+ commands = [
+ CommandDefinition().function("do setup"),
+ CommandDefinition().function("run tests").vars(run_tests_vars)
+ ]
+ task.dependency(TaskDependency("compile")).commands(commands)
+
+ for idx, suite in enumerate(suites):
+ sub_task_name = taskname.name_generated_task(options.task, idx, len(suites),
+ options.variant)
+ generate_task(suite.name, sub_task_name)
+
+ # Add the misc suite
+ misc_suite_name = "{0}_misc".format(options.suite)
+ generate_task(misc_suite_name, "{0}_misc_{1}".format(options.task, options.variant))
+
+ dt = DisplayTaskDefinition(options.task).execution_tasks(task_names) \
+ .execution_task("{0}_gen".format(options.task))
+ evg_config.variant(options.variant).tasks(task_specs).display_task(dt)
+
+ return evg_config
class Suite(object):
@@ -283,6 +388,7 @@ class Suite(object):
self.tests = []
self.total_runtime = 0
self.variant_runtime = defaultdict(int)
+ self._name = None
def add_test(self, test_name, test_data):
"""Add the given test to this suite."""
@@ -304,19 +410,6 @@ class Suite(object):
return len(self.tests)
- def get_model(self, extra_model_data=None):
- """Get a model of this suite that can be used to render a yml file."""
-
- model = {"test_names": self.tests, "variants": []}
- for variant in self.variant_runtime:
- model["variants"].append(
- {"name": variant, "runtime": self.variant_runtime[variant] / 60})
-
- if extra_model_data:
- model.update(extra_model_data)
-
- return model
-
class Main(object):
"""Orchestrate the execution of generate_resmoke_suites."""
@@ -325,6 +418,7 @@ class Main(object):
"""Initialize the object."""
self.deps = deps
self.options = {}
+ self.config_options = {}
self.commit_range = None
self.test_list = []
@@ -332,8 +426,10 @@ class Main(object):
"""Parse the command line options and return the parsed data."""
parser = argparse.ArgumentParser(description=self.main.__doc__)
+ parser.add_argument("--expansion-file", dest="expansion_file", type=str,
+ help="Location of expansions file generated by evergreen.")
parser.add_argument("--analysis-duration", dest="duration_days", default=14,
- help="Number of days to analyze.")
+ help="Number of days to analyze.", type=int)
parser.add_argument("--branch", dest="branch", default="master",
help="Branch of project to analyze.")
parser.add_argument("--end-commit", dest="end_commit", help="End analysis at this commit.")
@@ -350,9 +446,19 @@ class Main(object):
help="Comma-separated list of Evergreeen build variants to analyze.")
parser.add_argument("--max-sub-suites", dest="max_sub_suites", type=int,
help="Max number of suites to divide into.")
+ parser.add_argument("--resmoke-args", dest="resmoke_args",
+ help="Arguments to pass to resmoke calls.")
+ parser.add_argument("--resmoke-jobs_max", dest="resmoke_jobs_max",
+ help="Number of resmoke jobs to invoke.")
+ parser.add_argument("--suite", dest="suite",
+ help="Name of suite being split(defaults to task_name)")
+ parser.add_argument("--run-multiple-jobs", dest="run_multiple_jobs",
+ help="Should resmoke run multiple jobs")
+ parser.add_argument("--task_name", dest="task", help="Name of task to split.")
+ parser.add_argument("--variant", dest="build_variant",
+ help="Build variant being run against.")
parser.add_argument("--verbose", dest="verbose", action="store_true", default=False,
help="Enable verbose logging.")
- parser.add_argument("task", nargs=1, help="task to analyze.")
options = parser.parse_args()
@@ -360,6 +466,8 @@ class Main(object):
if not options.start_commit or not options.end_commit:
parser.error("--start-commit and --end-commit must both be specified")
+ self.config_options = get_config_options(options, options.expansion_file)
+
return options
def get_data(self, target, start_date, task, variants):
@@ -376,32 +484,17 @@ class Main(object):
return divide_tests_into_suites_by_maxtime(tests, self.test_list, execution_time_secs,
self.options.max_sub_suites)
- def render_suites(self, suites, task):
- """Render the given suites into yml files that can be used by resmoke.py."""
- for idx, suite in enumerate(suites):
- render_template(suite.get_model(self.extra_model_data()), task, idx)
-
- def render_misc_suite(self, task):
- """Render a misc suite to run any tests that might be added to the task directory."""
- model = get_misc_model(self.test_list, self.extra_model_data())
- source = "{dir}/{task}.yml.j2".format(dir=TEMPLATES_DIR, task=task)
- target = "{dir}/{task}_misc.yml".format(dir=TEST_SUITE_DIR, task=task)
-
- render(model, source, target)
-
- def extra_model_data(self):
- """Build extra data to include in the model."""
- return {
- "options": self.options,
- "start_commit": self.commit_range.start,
- "end_commit": self.commit_range.end,
- }
+ def write_evergreen_configuration(self, suites, task):
+ """Generate the evergreen configuration for the new suite and write it to disk."""
+ evg_config = generate_evg_config(suites, self.config_options)
+
+ with open(os.path.join(CONFIG_DIR, task + ".json"), "w") as file_handle:
+ file_handle.write(evg_config.to_json())
def main(self):
"""Generate resmoke suites that run within a specified target execution time."""
options = self.parse_commandline()
-
self.options = options
if options.verbose:
@@ -411,20 +504,21 @@ class Main(object):
self.commit_range = CommitRange(options.start_commit, options.end_commit)
LOGGER.debug("Starting execution for options %s", options)
- task = options.task[0]
-
today = datetime.datetime.utcnow().replace(microsecond=0)
start_date = today - datetime.timedelta(days=options.duration_days)
-
target = ProjectTarget(options.owner, options.project, options.branch)
- data = self.get_data(target, start_date, task, options.variants)
+ prepare_directory_for_suite(CONFIG_DIR)
+
+ data = self.get_data(target, start_date, self.config_options.task, options.variants)
suites = self.calculate_suites(data, options.execution_time_minutes * 60)
- LOGGER.debug("Creating %d suites", len(suites))
+ LOGGER.debug("Creating %d suites for %s", len(suites), self.config_options.task)
+
+ render_suite(suites, self.config_options.suite)
+ render_misc_suite(self.test_list, self.config_options.suite)
- self.render_suites(suites, task)
- self.render_misc_suite(task)
+ self.write_evergreen_configuration(suites, self.config_options.task)
if __name__ == "__main__":
diff --git a/buildscripts/resmokeconfig/suites/buildscripts_test.yml b/buildscripts/resmokeconfig/suites/buildscripts_test.yml
index 4886acb7b43..3184bb1fed9 100644
--- a/buildscripts/resmokeconfig/suites/buildscripts_test.yml
+++ b/buildscripts/resmokeconfig/suites/buildscripts_test.yml
@@ -8,7 +8,6 @@ selector:
- buildscripts/tests/resmokelib/test_archival.py # Requires boto3.
- buildscripts/tests/resmokelib/test_selector.py # Test assumes POSIX path.
- buildscripts/tests/test_aws_ec2.py # Requires boto3.
- - buildscripts/tests/test_evergreen_gen_fuzzer_tests.py # Requires mock.
- buildscripts/tests/test_remote_operations.py # Requires ssh to be enabled locally.
- buildscripts/tests/test_update_test_lifecycle.py # Test assumes POSIX path.
diff --git a/buildscripts/templates/generate_resmoke_suites/replica_sets_auth.yml.j2 b/buildscripts/templates/generate_resmoke_suites/replica_sets_auth.yml.j2
deleted file mode 100644
index e2f89521be6..00000000000
--- a/buildscripts/templates/generate_resmoke_suites/replica_sets_auth.yml.j2
+++ /dev/null
@@ -1,50 +0,0 @@
-# This file was generated by buildscripts/generate_resmoke_suites.py and manual edits should also be
-# made to replica_sets_auth.yml in order to ensure running the full test suite locally matches the
-# behavior of running parts of the test suite in Evergreen. It was generated against commit range:
-# {{ start_commit }} - {{ end_commit }}
-# with the following options:
-# {{ options }}
-
-# Section that is ignored by resmoke.py.
-config_variables:
-- &keyFile jstests/libs/authTestsKey
-- &keyFileData Thiskeyisonlyforrunningthesuitewithauthenticationdontuseitinanytestsdirectly
-
-test_kind: js_test
-
-selector:
-{% if variants is defined %}
- # The following tests take approximately
-{% for variant in variants %}
- # {{ variant.runtime }} minutes to run on {{ variant.name }}
-{% endfor %}
-{% endif %}
- roots:
-{% for test in test_names %}
- - {{ test }}
-{% endfor %}
-{% if is_misc is defined %}
- - jstests/replsets/*.js
- exclude_files:
- # Skip any tests that run with auth explicitly.
- - jstests/replsets/*[aA]uth*.js
- # Also skip tests that require a ScopedThread, because ScopedThreads don't inherit credentials.
- - jstests/replsets/interrupted_batch_insert.js
-{% if excluded_tests is defined %}
-{% for test in excluded_tests %}
- - {{ test }}
-{% endfor %}
-{% endif %}
-{% endif %}
-
-executor:
- config:
- shell_options:
- global_vars:
- TestData:
- auth: true
- authMechanism: SCRAM-SHA-1
- keyFile: *keyFile
- keyFileData: *keyFileData
- nodb: ''
- readMode: commands
diff --git a/buildscripts/templates/generate_resmoke_suites/replica_sets_ese.yml.j2 b/buildscripts/templates/generate_resmoke_suites/replica_sets_ese.yml.j2
deleted file mode 100644
index 041cbfe22f8..00000000000
--- a/buildscripts/templates/generate_resmoke_suites/replica_sets_ese.yml.j2
+++ /dev/null
@@ -1,42 +0,0 @@
-# This file was generated by buildscripts/generate_resmoke_suites.py and manual edits should also be
-# made to replica_sets_ese.yml in order to ensure running the full test suite locally matches the
-# behavior of running parts of the test suite in Evergreen. It was generated against commit range:
-# {{ start_commit }} - {{ end_commit }}
-# with the following options:
-# {{ options }}
-
-# Section that is ignored by resmoke.py.
-config_variables:
-- &keyFile src/mongo/db/modules/enterprise/jstests/encryptdb/libs/ekf2
-
-test_kind: js_test
-
-selector:
-{% if variants is defined %}
- # The following tests take approximately
-{% for variant in variants %}
- # {{ variant.runtime }} minutes to run on {{ variant.name }}
-{% endfor %}
-{% endif %}
- roots:
-{% for test in test_names %}
- - {{ test }}
-{% endfor %}
-{% if is_misc is defined %}
- - jstests/replsets/*.js
-{% if excluded_tests is defined %}
-{% for test in excluded_tests %}
- - {{ test }}
-{% endfor %}
-{% endif %}
-{% endif %}
-
-executor:
- config:
- shell_options:
- nodb: ''
- global_vars:
- TestData:
- enableEncryption: ''
- encryptionKeyFile: *keyFile
- readMode: commands
diff --git a/buildscripts/templates/generate_resmoke_suites/sharding.yml.j2 b/buildscripts/templates/generate_resmoke_suites/sharding.yml.j2
deleted file mode 100644
index f680444cbbc..00000000000
--- a/buildscripts/templates/generate_resmoke_suites/sharding.yml.j2
+++ /dev/null
@@ -1,34 +0,0 @@
-# This file was generated by buildscripts/generate_resmoke_suites.py and manual edits should also be
-# made to sharding.yml in order to ensure running the full test suite locally matches the behavior
-# of running parts of the test suite in Evergreen. It was generated against commit range:
-# {{ start_commit }} - {{ end_commit }}
-# with the following options:
-# {{ options }}
-test_kind: js_test
-
-selector:
-{% if variants is defined %}
- # The following tests take approximately
-{% for variant in variants %}
- # {{ variant.runtime }} minutes to run on {{ variant.name }}
-{% endfor %}
-{% endif %}
- roots:
-{% for test in test_names %}
- - {{ test }}
-{% endfor %}
-{% if is_misc is defined %}
- - jstests/sharding/*.js
-{% endif %}
-{% if excluded_tests is defined %}
- exclude_files:
-{% for test in excluded_tests %}
- - {{ test }}
-{% endfor %}
-{% endif %}
-
-executor:
- config:
- shell_options:
- nodb: ''
- readMode: commands
diff --git a/buildscripts/templates/generate_resmoke_suites/sharding_auth.yml.j2 b/buildscripts/templates/generate_resmoke_suites/sharding_auth.yml.j2
deleted file mode 100644
index f7beb4884f1..00000000000
--- a/buildscripts/templates/generate_resmoke_suites/sharding_auth.yml.j2
+++ /dev/null
@@ -1,71 +0,0 @@
-# This file was generated by buildscripts/generate_resmoke_suites.py and manual edits should also be
-# made to sharding_auth.yml in order to ensure running the full test suite locally matches the
-# behavior of running parts of the test suite in Evergreen. It was generated against commit range:
-# {{ start_commit }} - {{ end_commit }}
-# with the following options:
-# {{ options }}
-
-# Section that is ignored by resmoke.py.
-config_variables:
-- &keyFile jstests/libs/authTestsKey
-- &keyFileData Thiskeyisonlyforrunningthesuitewithauthenticationdontuseitinanytestsdirectly
-
-test_kind: js_test
-
-selector:
-{% if variants is defined %}
- # The following tests take approximately
-{% for variant in variants %}
- # {{ variant.runtime }} minutes to run on {{ variant.name }}
-{% endfor %}
-{% endif %}
- roots:
-{% for test in test_names %}
- - {{ test }}
-{% endfor %}
-{% if is_misc is defined %}
- - jstests/sharding/*.js
- exclude_files:
- # Skip any tests that run with auth explicitly.
- - jstests/sharding/*[aA]uth*.js
- - jstests/sharding/advance_cluster_time_action_type.js
- - jstests/sharding/aggregation_currentop.js # SERVER-19318
- - jstests/sharding/kill_sessions.js
- # Skip these additional tests when running with auth enabled.
- - jstests/sharding/parallel.js
- # Skip these tests that run with enableTestCommands off.
- - jstests/sharding/shard_config_db_collections.js
- # Skip the testcases that do not have auth bypass when running ops in parallel.
- - jstests/sharding/cleanup_orphaned_cmd_during_movechunk.js # SERVER-21713
- - jstests/sharding/cleanup_orphaned_cmd_during_movechunk_hashed.js # SERVER-21713
- - jstests/sharding/migration_ignore_interrupts_1.js # SERVER-21713
- - jstests/sharding/migration_ignore_interrupts_2.js # SERVER-21713
- - jstests/sharding/migration_ignore_interrupts_3.js # SERVER-21713
- - jstests/sharding/migration_ignore_interrupts_4.js # SERVER-21713
- - jstests/sharding/migration_move_chunk_after_receive.js # SERVER-21713
- - jstests/sharding/migration_server_status.js # SERVER-21713
- - jstests/sharding/migration_sets_fromMigrate_flag.js # SERVER-21713
- - jstests/sharding/migration_with_source_ops.js # SERVER-21713
- - jstests/sharding/movechunk_interrupt_at_primary_stepdown.js # SERVER-21713
- - jstests/sharding/movechunk_parallel.js # SERVER-21713
- - jstests/sharding/migration_critical_section_concurrency.js # SERVER-21713
- # Runs with auth enabled.
- - jstests/sharding/mongod_returns_no_cluster_time_without_keys.js
-{% if excluded_tests is defined %}
-{% for test in excluded_tests %}
- - {{ test }}
-{% endfor %}
-{% endif %}
-{% endif %}
-
-executor:
- config:
- shell_options:
- global_vars:
- TestData:
- auth: true
- authMechanism: SCRAM-SHA-1
- keyFile: *keyFile
- keyFileData: *keyFileData
- nodb: ''
- readMode: commands
diff --git a/buildscripts/templates/generate_resmoke_suites/sharding_auth_audit.yml.j2 b/buildscripts/templates/generate_resmoke_suites/sharding_auth_audit.yml.j2
deleted file mode 100644
index 8c7a770fd5e..00000000000
--- a/buildscripts/templates/generate_resmoke_suites/sharding_auth_audit.yml.j2
+++ /dev/null
@@ -1,73 +0,0 @@
-# This file was generated by buildscripts/generate_resmoke_suites.py and manual edits should also be
-# made to sharding_auth_audit.yml in order to ensure running the full test
-# suite locally matches the behavior of running parts of the test suite in Evergreen. It was
-# generated against commit range:
-# {{ start_commit }} - {{ end_commit }}
-# with the following options:
-# {{ options }}
-
-# Section that is ignored by resmoke.py.
-config_variables:
-- &keyFile jstests/libs/authTestsKey
-- &keyFileData Thiskeyisonlyforrunningthesuitewithauthenticationdontuseitinanytestsdirectly
-
-test_kind: js_test
-
-selector:
-{% if variants is defined %}
- # The following tests take approximately
-{% for variant in variants %}
- # {{ variant.runtime }} minutes to run on {{ variant.name }}
-{% endfor %}
-{% endif %}
- roots:
-{% for test in test_names %}
- - {{ test }}
-{% endfor %}
-{% if is_misc is defined %}
- - jstests/sharding/*.js
- exclude_files:
- # Skip any tests that run with auth explicitly.
- - jstests/sharding/*[aA]uth*.js
- - jstests/sharding/advance_cluster_time_action_type.js
- - jstests/sharding/aggregation_currentop.js # SERVER-19318
- - jstests/sharding/kill_sessions.js
- # Skip these additional tests when running with auth enabled.
- - jstests/sharding/parallel.js
- # Skip these tests that run with enableTestCommands off.
- - jstests/sharding/shard_config_db_collections.js
- # Skip the testcases that do not have auth bypass when running ops in parallel.
- - jstests/sharding/cleanup_orphaned_cmd_during_movechunk.js # SERVER-21713
- - jstests/sharding/cleanup_orphaned_cmd_during_movechunk_hashed.js # SERVER-21713
- - jstests/sharding/migration_with_source_ops.js # SERVER-21713
- - jstests/sharding/migration_sets_fromMigrate_flag.js # SERVER-21713
- - jstests/sharding/migration_ignore_interrupts_1.js # SERVER-21713
- - jstests/sharding/migration_ignore_interrupts_2.js # SERVER-21713
- - jstests/sharding/migration_ignore_interrupts_3.js # SERVER-21713
- - jstests/sharding/migration_ignore_interrupts_4.js # SERVER-21713
- - jstests/sharding/movechunk_interrupt_at_primary_stepdown.js # SERVER-21713
- - jstests/sharding/movechunk_parallel.js # SERVER-21713
- - jstests/sharding/migration_server_status.js # SERVER-21713
- - jstests/sharding/migration_move_chunk_after_receive.js # SERVER-21713
- - jstests/sharding/migration_critical_section_concurrency.js # SERVER-21713
- # Runs with auth enabled.
- - jstests/sharding/mongod_returns_no_cluster_time_without_keys.js
-{% if excluded_tests is defined %}
-{% for test in excluded_tests %}
- - {{ test }}
-{% endfor %}
-{% endif %}
-{% endif %}
-
-executor:
- config:
- shell_options:
- global_vars:
- TestData:
- auditDestination: 'console'
- auth: true
- authMechanism: SCRAM-SHA-1
- keyFile: *keyFile
- keyFileData: *keyFileData
- nodb: ''
- readMode: commands
diff --git a/buildscripts/templates/generate_resmoke_suites/sharding_ese.yml.j2 b/buildscripts/templates/generate_resmoke_suites/sharding_ese.yml.j2
deleted file mode 100644
index 16cd54389e4..00000000000
--- a/buildscripts/templates/generate_resmoke_suites/sharding_ese.yml.j2
+++ /dev/null
@@ -1,43 +0,0 @@
-# This file was generated by buildscripts/generate_resmoke_suites.py and manual edits should also be
-# made to sharding_ese.yml in order to ensure running the full test suite locally matches the
-# behavior of running parts of the test suite in Evergreen. It was generated against commit range:
-# {{ start_commit }} - {{ end_commit }}
-# with the following options:
-# {{ options }}
-
-# Section that is ignored by resmoke.py.
-config_variables:
-- &keyFile src/mongo/db/modules/enterprise/jstests/encryptdb/libs/ekf2
-
-test_kind: js_test
-
-selector:
-{% if variants is defined %}
- # The following tests take approximately
-{% for variant in variants %}
- # {{ variant.runtime }} minutes to run on {{ variant.name }}
-{% endfor %}
-{% endif %}
- roots:
-{% for test in test_names %}
- - {{ test }}
-{% endfor %}
-{% if is_misc is defined %}
- - jstests/sharding/*.js
- exclude_files:
-{% if excluded_tests is defined %}
-{% for test in excluded_tests %}
- - {{ test }}
-{% endfor %}
-{% endif %}
-{% endif %}
-
-executor:
- config:
- shell_options:
- nodb: ''
- global_vars:
- TestData:
- enableEncryption: ''
- encryptionKeyFile: *keyFile
- readMode: commands
diff --git a/buildscripts/templates/generate_resmoke_suites/sharding_last_stable_mongos_and_mixed_shards.yml.j2 b/buildscripts/templates/generate_resmoke_suites/sharding_last_stable_mongos_and_mixed_shards.yml.j2
deleted file mode 100644
index 76176065e79..00000000000
--- a/buildscripts/templates/generate_resmoke_suites/sharding_last_stable_mongos_and_mixed_shards.yml.j2
+++ /dev/null
@@ -1,97 +0,0 @@
-# This file was generated by buildscripts/generate_resmoke_suites.py and manual edits should also be
-# made to sharding_last_stable_mongos_and_mixed_shards.yml in order to ensure running the full test
-# suite locally matches the behavior of running parts of the test suite in Evergreen. It was
-# generated against commit range:
-# {{ start_commit }} - {{ end_commit }}
-# with the following options:
-# {{ options }}
-
-test_kind: js_test
-
-selector:
-{% if variants is defined %}
- # The following tests take approximately
-{% for variant in variants %}
- # {{ variant.runtime }} minutes to run on {{ variant.name }}
-{% endfor %}
-{% endif %}
- roots:
-{% for test in test_names %}
- - {{ test }}
-{% endfor %}
-{% if is_misc is defined %}
- - jstests/sharding/*.js
- exclude_files:
- # Will always fail on last-stable. In order for the test to succeed, the setFCV
- # command has to reach the shards. Since the cluster will already be
- # running in fully downgraded version, the config server won't forward the
- # command to the shards - it'll just return success immediately.
- - jstests/sharding/max_time_ms_sharded_new_commands.js
- # Requires fix to SERVER-31689
- - jstests/sharding/aggregation_currentop.js
- # SERVER-33683: We added a restriction on using an aggregation within a transaction against
- # mongos. This should be removed and the test can be adjusted and re-added to this passthrough.
- - jstests/sharding/aggregations_in_session.js
- # New waitForClusterTime
- - jstests/sharding/auth_slaveok_routing.js
- # This test should not be run with a mixed cluster environment.
- - jstests/sharding/nonreplicated_uuids_on_shardservers.js
- # Enable when SERVER-33538 is backported.
- - jstests/sharding/mapReduce_outSharded_checkUUID.js
- # Will always fail because we can't downgrade FCV before the last-stable binary mongos connects,
- # meaning that either the test will stall, or mongos will crash due to connecting to an upgraded
- # FCV cluster.
- - jstests/sharding/mongos_wait_csrs_initiate.js
- # Enable if SERVER-34971 is backported or 4.2 becomes last-stable
- - jstests/sharding/update_replace_id.js
- - jstests/sharding/stale_mongos_updates_and_removes.js
- - jstests/sharding/geo_near_sharded.js
- # Enable when 4.2 becomes last-stable.
- - jstests/sharding/collation_targeting.js
- - jstests/sharding/collation_targeting_inherited.js
- - jstests/sharding/geo_near_random1.js
- - jstests/sharding/geo_near_random2.js
- - jstests/sharding/restart_transactions.js
- - jstests/sharding/shard7.js
- - jstests/sharding/shard_collection_existing_zones.js
- - jstests/sharding/snapshot_cursor_commands_mongos.js
- - jstests/sharding/transactions_error_labels.js
- - jstests/sharding/transactions_implicit_abort.js
- - jstests/sharding/transactions_multi_writes.js
- - jstests/sharding/transactions_read_concerns.js
- - jstests/sharding/transactions_recover_decision_from_local_participant.js
- - jstests/sharding/transactions_reject_writes_for_moved_chunks.js
- - jstests/sharding/transactions_snapshot_errors_first_statement.js
- - jstests/sharding/transactions_snapshot_errors_subsequent_statements.js
- - jstests/sharding/transactions_stale_database_version_errors.js
- - jstests/sharding/transactions_stale_shard_version_errors.js
- - jstests/sharding/transactions_target_at_point_in_time.js
- - jstests/sharding/transactions_view_resolution.js
- - jstests/sharding/transactions_writes_not_retryable.js
- - jstests/sharding/txn_agg.js
- - jstests/sharding/txn_basic_two_phase_commit.js
- - jstests/sharding/txn_coordinator_commands_basic_requirements.js
- - jstests/sharding/txn_writes_during_movechunk.js
- - jstests/sharding/update_sharded.js
- - jstests/sharding/shard_existing_coll_chunk_count.js
- - jstests/sharding/failcommand_failpoint_not_parallel.js
- - jstests/sharding/transactions_expiration.js
- # Enable if SERVER-20865 is backported or 4.2 becomes last-stable
- - jstests/sharding/sharding_statistics_server_status.js
-
-{% if excluded_tests is defined %}
-{% for test in excluded_tests %}
- - {{ test }}
-{% endfor %}
-{% endif %}
-{% endif %}
-
-executor:
- config:
- shell_options:
- global_vars:
- TestData:
- mongosBinVersion: 'last-stable'
- shardMixedBinVersions: true
- skipCheckingUUIDsConsistentAcrossCluster: true
- nodb: ''
diff --git a/buildscripts/tests/test_evergreen_gen_fuzzer_tests.py b/buildscripts/tests/test_evergreen_gen_fuzzer_tests.py
index 1956aa97519..cddb2379ea6 100644
--- a/buildscripts/tests/test_evergreen_gen_fuzzer_tests.py
+++ b/buildscripts/tests/test_evergreen_gen_fuzzer_tests.py
@@ -10,42 +10,6 @@ from buildscripts import evergreen_gen_fuzzer_tests as gft
# pylint: disable=missing-docstring,protected-access
-class TestGetConfigValue(unittest.TestCase):
- def test_undefined_values_return_none(self):
- value = gft._get_config_value("unknown", {}, {})
-
- self.assertEqual(None, value)
-
- def test_default_can_be_specified(self):
- value = gft._get_config_value("option", {}, {}, default="default")
-
- self.assertEqual("default", value)
-
- def test_exception_throw_for_missing_required(self):
- self.assertRaises(ValueError, gft._get_config_value, "missing", {}, {}, required=True)
-
- def test_config_file_value_is_used(self):
- value = gft._get_config_value("option", {}, {"option": "value 0"}, default="default",
- required=True)
- self.assertEqual("value 0", value)
-
- def test_cmdline_value_is_used(self):
- cmdline_mock = mock.Mock
- cmdline_mock.option = "cmdline value"
- value = gft._get_config_value("option", cmdline_mock, {"option": "value 0"},
- default="default", required=True)
-
- self.assertEqual("cmdline value", value)
-
-
-class TestNameTask(unittest.TestCase):
- def test_name_task_with_width_one(self):
- self.assertEqual("name_3", gft._name_task("name", 3, 10))
-
- def test_name_task_with_width_four(self):
- self.assertEqual("task_3141", gft._name_task("task", 3141, 5000))
-
-
class TestGenerateEvgTasks(unittest.TestCase):
@staticmethod
def _create_options_mock():
diff --git a/buildscripts/tests/test_generate_resmoke_suites.py b/buildscripts/tests/test_generate_resmoke_suites.py
index efb69f061ed..e5501f19548 100644
--- a/buildscripts/tests/test_generate_resmoke_suites.py
+++ b/buildscripts/tests/test_generate_resmoke_suites.py
@@ -3,11 +3,16 @@
from __future__ import absolute_import
import datetime
+import math
+import os
import unittest
+import yaml
-from mock import patch, Mock
+from mock import patch, mock_open, call, Mock
from buildscripts import generate_resmoke_suites as grs
+from generate_resmoke_suites import render_suite, render_misc_suite, \
+ prepare_directory_for_suite
# pylint: disable=missing-docstring,invalid-name,unused-argument,no-self-use
@@ -251,8 +256,8 @@ class OrganizeExecutionsByTestTest(unittest.TestCase):
self.assertEquals(len(tests), 0)
@patch("buildscripts.generate_resmoke_suites.os")
- def test_only_test_executions(self, os):
- os.path.isfile.return_value = True
+ def test_only_test_executions(self, mock_os):
+ mock_os.path.isfile.return_value = True
executions = [{
"revision": "revision1",
@@ -279,8 +284,8 @@ class OrganizeExecutionsByTestTest(unittest.TestCase):
self.assertEquals(tests["test3.js"]["variant1"], 3)
@patch("buildscripts.generate_resmoke_suites.os")
- def test_mix_of_test_and_hook_executions(self, os):
- os.path.isfile.return_value = True
+ def test_mix_of_test_and_hook_executions(self, mock_os):
+ mock_os.path.isfile.return_value = True
executions = [
{
@@ -323,8 +328,8 @@ class OrganizeExecutionsByTestTest(unittest.TestCase):
self.assertEquals(tests["test3.js"]["variant1"], 6)
@patch("buildscripts.generate_resmoke_suites.os")
- def test_multiple_revisions_for_same_test(self, os):
- os.path.isfile.return_value = True
+ def test_multiple_revisions_for_same_test(self, mock_os):
+ mock_os.path.isfile.return_value = True
executions = [
{
@@ -373,8 +378,8 @@ class OrganizeExecutionsByTestTest(unittest.TestCase):
self.assertEquals(tests["test3.js"]["variant1"], 3)
@patch("buildscripts.generate_resmoke_suites.os")
- def test_non_files_are_not_included(self, os):
- os.path.isfile.return_value = False
+ def test_non_files_are_not_included(self, mock_os):
+ mock_os.path.isfile.return_value = False
executions = [{
"revision": "revision1",
@@ -520,62 +525,150 @@ class SuiteTest(unittest.TestCase):
self.assertEqual(suite.get_test_count(), 3)
self.assertEqual(suite.get_runtime(), 29)
- def test_model_generation(self):
- suite = grs.Suite()
- suite.add_test('test1', {
- "max_runtime": 10 * 60,
- "variant1": 5 * 60,
- "variant2": 10 * 60,
- "variant3": 7 * 60,
- })
- suite.add_test('test2', {
- "max_runtime": 12 * 60,
- "variant1": 12 * 60,
- "variant2": 8 * 60,
- "variant3": 6 * 60,
- })
- suite.add_test('test3', {
- "max_runtime": 7 * 60,
- "variant1": 6 * 60,
- "variant2": 6 * 60,
- "variant3": 7 * 60,
- })
-
- model = suite.get_model()
- self.assertEqual(model["test_names"], ["test1", "test2", "test3"])
- self.assertIn({"runtime": 23, "name": "variant1"}, model["variants"])
- self.assertIn({"runtime": 24, "name": "variant2"}, model["variants"])
- self.assertIn({"runtime": 20, "name": "variant3"}, model["variants"])
+def create_suite(count=3, start=0):
+ """ Create a suite with count tests."""
+ suite = grs.Suite()
+ for i in range(start, start + count):
+ suite.add_test('test{}'.format(i), {})
+ return suite
-class GetMiscModelTest(unittest.TestCase):
- def test_model_with_test_in_same_dir(self):
- test_list = [
- "dir0/subdir0/test0",
- "dir0/subdir0/test1",
- "dir0/subdir0/test2",
- "dir0/subdir0/test3",
- ]
+class RenderSuites(unittest.TestCase):
+ EXPECTED_FORMAT = """selector:
+ excludes:
+ - fixed
+ roots:
+ - test{}
+ - test{}
+ - test{}
+"""
- model = grs.get_misc_model(test_list)
+ def _test(self, size):
- self.assertIn("is_misc", model)
-
- self.assertIn("excluded_tests", model)
- self.assertEqual(len(model["excluded_tests"]), 4)
- self.assertIn("dir0/subdir0/test0", model["excluded_tests"])
- self.assertIn("dir0/subdir0/test1", model["excluded_tests"])
- self.assertIn("dir0/subdir0/test2", model["excluded_tests"])
- self.assertIn("dir0/subdir0/test3", model["excluded_tests"])
+ suites = [create_suite(start=3 * i) for i in range(size)]
+ expected = [
+ self.EXPECTED_FORMAT.format(*range(3 * i, 3 * (i + 1))) for i in range(len(suites))
+ ]
- def test_model_includes_extra_data(self):
- test_list = ["dir0/subdir0/test0"]
- extra_data = {
- "extra": "data",
- }
+ m = mock_open(read_data=yaml.dump({'selector': {'roots': [], 'excludes': ['fixed']}}))
+ with patch('generate_resmoke_suites.open', m, create=True):
+ render_suite(suites, 'suite_name')
+ handle = m()
+
+ # The other writes are for the headers.
+ self.assertEquals(len(suites) * 2, handle.write.call_count)
+ handle.write.assert_has_calls([call(e) for e in expected], any_order=True)
+ calls = [
+ call(os.path.join(grs.TEST_SUITE_DIR, 'suite_name.yml'), 'r')
+ for _ in range(len(suites))
+ ]
+ m.assert_has_calls(calls, any_order=True)
+ filename = os.path.join(grs.CONFIG_DIR, 'suite_name_{{:0{}}}.yml'.format(
+ int(math.ceil(math.log10(size)))))
+ calls = [call(filename.format(i), 'w') for i in range(size)]
+ m.assert_has_calls(calls, any_order=True)
+
+ def test_1_suite(self):
+ self._test(1)
+
+ def test_11_suites(self):
+ self._test(11)
+
+ def test_101_suites(self):
+ self._test(101)
+
+
+class RenderMiscSuites(unittest.TestCase):
+ def test_single_suite(self):
+
+ test_list = ['test{}'.format(i) for i in range(10)]
+ m = mock_open(read_data=yaml.dump({'selector': {'roots': []}}))
+ with patch('generate_resmoke_suites.open', m, create=True):
+ render_misc_suite(test_list, 'suite_name')
+ handle = m()
+
+ # The other writes are for the headers.
+ self.assertEquals(2, handle.write.call_count)
+ handle.write.assert_any_call("""selector:
+ exclude_files:
+ - test0
+ - test1
+ - test2
+ - test3
+ - test4
+ - test5
+ - test6
+ - test7
+ - test8
+ - test9
+ roots: []
+""")
+ calls = [call(os.path.join(grs.TEST_SUITE_DIR, 'suite_name.yml'), 'r')]
+ m.assert_has_calls(calls, any_order=True)
+ filename = os.path.join(grs.CONFIG_DIR, 'suite_name_misc.yml')
+ calls = [call(filename, 'w')]
+ m.assert_has_calls(calls, any_order=True)
+
+
+class PrepareDirectoryForSuite(unittest.TestCase):
+ def test_no_directory(self):
+ with patch('generate_resmoke_suites.os') as mock_os:
+ mock_os.path.exists.return_value = False
+ prepare_directory_for_suite('tmp')
+
+ mock_os.makedirs.assert_called_once_with('tmp')
+
+
+class GenerateEvgConfigTest(unittest.TestCase):
+ @staticmethod
+ def generate_mock_suites(count):
+ suites = []
+ for idx in range(count):
+ suite = Mock()
+ suite.name = "suite {0}".format(idx)
+ suites.append(suite)
- model = grs.get_misc_model(test_list, extra_data)
+ return suites
- self.assertIn("extra", model)
- self.assertEqual(model["extra"], "data")
+ @staticmethod
+ def generate_mock_options():
+ options = Mock()
+ options.resmoke_args = "resmoke_args"
+ options.run_multiple_jobs = "true"
+ options.variant = "buildvariant"
+ options.suite = "suite"
+ options.task = "suite"
+
+ return options
+
+ def test_evg_config_is_created(self):
+ options = self.generate_mock_options()
+ suites = self.generate_mock_suites(3)
+
+ config = grs.generate_evg_config(suites, options).to_map()
+
+ self.assertEqual(len(config["tasks"]), len(suites) + 1)
+ command1 = config["tasks"][0]["commands"][1]
+ self.assertIn(options.resmoke_args, command1["vars"]["resmoke_args"])
+ self.assertIn(options.run_multiple_jobs, command1["vars"]["run_multiple_jobs"])
+ self.assertEqual("run tests", command1["func"])
+
+ def test_evg_config_is_created_with_diff_task_and_suite(self):
+ options = self.generate_mock_options()
+ options.task = "task"
+ suites = self.generate_mock_suites(3)
+
+ config = grs.generate_evg_config(suites, options).to_map()
+
+ self.assertEqual(len(config["tasks"]), len(suites) + 1)
+ display_task = config["buildvariants"][0]["display_tasks"][0]
+ self.assertEqual(options.task, display_task["name"])
+ self.assertEqual(len(suites) + 2, len(display_task["execution_tasks"]))
+ self.assertIn(options.task + "_gen", display_task["execution_tasks"])
+ self.assertIn(options.task + "_misc_" + options.variant, display_task["execution_tasks"])
+
+ task = config["tasks"][0]
+ self.assertIn(options.variant, task["name"])
+ self.assertIn(task["name"], display_task["execution_tasks"])
+ self.assertIn(options.suite, task["commands"][1]["vars"]["resmoke_args"])
diff --git a/buildscripts/tests/util/test_read_config.py b/buildscripts/tests/util/test_read_config.py
new file mode 100644
index 00000000000..c3887fcd8ec
--- /dev/null
+++ b/buildscripts/tests/util/test_read_config.py
@@ -0,0 +1,38 @@
+"""Unit tests for the util/read_config.py file."""
+
+from __future__ import absolute_import
+
+import unittest
+import mock
+
+from buildscripts.util import read_config as read_config
+
+# pylint: disable=missing-docstring,protected-access
+
+
+class TestGetConfigValue(unittest.TestCase):
+ def test_undefined_values_return_none(self):
+ value = read_config.get_config_value("unknown", {}, {})
+
+ self.assertEqual(None, value)
+
+ def test_default_can_be_specified(self):
+ value = read_config.get_config_value("option", {}, {}, default="default")
+
+ self.assertEqual("default", value)
+
+ def test_exception_throw_for_missing_required(self):
+ self.assertRaises(KeyError, read_config.get_config_value, "missing", {}, {}, required=True)
+
+ def test_config_file_value_is_used(self):
+ value = read_config.get_config_value("option", {}, {"option": "value 0"}, default="default",
+ required=True)
+ self.assertEqual("value 0", value)
+
+ def test_cmdline_value_is_used(self):
+ cmdline_mock = mock.Mock
+ cmdline_mock.option = "cmdline value"
+ value = read_config.get_config_value("option", cmdline_mock, {"option": "value 0"},
+ default="default", required=True)
+
+ self.assertEqual("cmdline value", value)
diff --git a/buildscripts/tests/util/test_taskname.py b/buildscripts/tests/util/test_taskname.py
new file mode 100644
index 00000000000..cf2811c7bca
--- /dev/null
+++ b/buildscripts/tests/util/test_taskname.py
@@ -0,0 +1,17 @@
+"""Unit tests for the util/taskname.py script."""
+
+from __future__ import absolute_import
+
+import unittest
+
+from buildscripts.util import taskname as taskname
+
+# pylint: disable=missing-docstring,protected-access
+
+
+class TestNameTask(unittest.TestCase):
+ def test_name_task_with_width_one(self):
+ self.assertEqual("name_3_var", taskname.name_generated_task("name", 3, 10, "var"))
+
+ def test_name_task_with_width_four(self):
+ self.assertEqual("task_3141_var", taskname.name_generated_task("task", 3141, 5000, "var"))
diff --git a/buildscripts/util/read_config.py b/buildscripts/util/read_config.py
new file mode 100644
index 00000000000..aad2b9a150c
--- /dev/null
+++ b/buildscripts/util/read_config.py
@@ -0,0 +1,44 @@
+"""Functions to read configuration files."""
+import yaml
+
+
+def get_config_value(attrib, cmd_line_options, config_file_data, required=False, default=None):
+ """
+ Get the configuration value to use.
+
+ First use command line options, then config file option, then the default. If required is
+ true, throw an exception if the value is not found.
+
+ :param attrib: Attribute to search for.
+ :param cmd_line_options: Command line options.
+ :param config_file_data: Config file data.
+ :param required: Is this option required.
+ :param default: Default value if option is not found.
+ :return: value to use for this option.
+ """
+ value = getattr(cmd_line_options, attrib, None)
+ if value is not None:
+ return value
+
+ if attrib in config_file_data:
+ return config_file_data[attrib]
+
+ if required:
+ raise KeyError("{0} must be specified".format(attrib))
+
+ return default
+
+
+def read_config_file(config_file):
+ """
+ Read the yaml config file specified.
+
+ :param config_file: path to config file.
+ :return: Object representing contents of config file.
+ """
+ config_file_data = {}
+ if config_file:
+ with open(config_file) as file_handle:
+ config_file_data = yaml.load(file_handle)
+
+ return config_file_data
diff --git a/buildscripts/util/taskname.py b/buildscripts/util/taskname.py
new file mode 100644
index 00000000000..ee9852e2295
--- /dev/null
+++ b/buildscripts/util/taskname.py
@@ -0,0 +1,20 @@
+"""Functions for working with resmoke task names."""
+
+import math
+
+
+def name_generated_task(parent_name, task_index, total_tasks, variant=None):
+ """
+ Create a zero-padded sub-task name.
+
+ :param parent_name: Name of the parent task.
+ :param task_index: Index of this sub-task.
+ :param total_tasks: Total number of sub-tasks being generated.
+ :return: Zero-padded name of sub-task.
+ """
+ suffix = ""
+ if variant:
+ suffix = "_{0}".format(variant)
+
+ index_width = int(math.ceil(math.log10(total_tasks)))
+ return "{0}_{1}{2}".format(parent_name, str(task_index).zfill(index_width), suffix)
diff --git a/etc/evergreen.yml b/etc/evergreen.yml
index d3b911b4e3b..e2e9024ce27 100644
--- a/etc/evergreen.yml
+++ b/etc/evergreen.yml
@@ -1846,7 +1846,7 @@ functions:
set -o verbose
${activate_virtualenv}
- $python -m pip install git+https://github.com/evergreen-ci/shrub.py
+ $python -m pip install -r etc/pip/evgtest-requirements.txt
$python buildscripts/evergreen_gen_fuzzer_tests.py --expansion-file expansions.yml
- command: archive.targz_pack
diff --git a/etc/pip/components/resmoke.req b/etc/pip/components/resmoke.req
index 9bcc7c6f684..8417ea6b721 100644
--- a/etc/pip/components/resmoke.req
+++ b/etc/pip/components/resmoke.req
@@ -1,3 +1,4 @@
mock; python_version < "3"
PyKMIP == 0.4.0; python_version < "3" # It's now 0.8.0. We're far enough back to have API conflicts.
jinja2
+shrub.py \ No newline at end of file
diff --git a/etc/pip/constraints.txt b/etc/pip/constraints.txt
index 27662e16b20..e84f5015f43 100644
--- a/etc/pip/constraints.txt
+++ b/etc/pip/constraints.txt
@@ -7,9 +7,9 @@
# Common requirements
asn1crypto==0.24.0
astroid==1.6.5
-boto3==1.9.23
-botocore==1.12.23
-certifi==2018.10.15
+boto3==1.9.57
+botocore==1.12.57
+certifi==2018.11.29
cffi==1.11.5
chardet==3.0.4
cryptography==2.3
@@ -21,28 +21,29 @@ Jinja2==2.10
jira==2.0.0
jmespath==0.9.3
lazy-object-proxy==1.3.1
-MarkupSafe==1.0
+MarkupSafe==1.1.0
mccabe==0.6.1
oauthlib==2.1.0
-pbr==4.3.0
-psutil==5.4.7
+pbr==5.1.1
+psutil==5.4.8
pycparser==2.19
pydocstyle==2.1.1
-PyJWT==1.6.4
+PyJWT==1.7.0
pylint==1.9.3
pymongo==3.5.1
-python-dateutil==2.7.3
+python-dateutil==2.7.5
PyYAML==3.13
-regex==2018.8.29
-requests==2.19.1
+regex==2018.11.22
+requests==2.20.1
requests-oauth==0.4.1
requests-oauthlib==1.0.0
requests-toolbelt==0.8.0
s3transfer==0.1.13
+shrub.py==0.1.0
six==1.11.0
snowballstemmer==1.2.1
typing==3.6.6
-urllib3==1.23
+urllib3==1.24.1
wrapt==1.10.11
yapf==0.21.0