From 3d437df7497827ed46624403bc19d72cd1935be9 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Mon, 6 Jan 2014 10:17:59 -0800 Subject: Add envs for different sqlalchemy versions Adjust tests to skip the sqlalchemy test if sqlalchemy is not installed. Adjust examples to fallback to a directory based backend if the sqlalchemy does not load or is not available. Include a updated tox.ini (generated from the toxgen.py script) that includes the new venv variations. Change-Id: I7686f09901a9b65d7c81b4e037b5bffc24aa7ef7 --- tools/toxgen.py | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100755 tools/toxgen.py (limited to 'tools') diff --git a/tools/toxgen.py b/tools/toxgen.py new file mode 100755 index 0000000..111ed9b --- /dev/null +++ b/tools/toxgen.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python + +# From: https://bitbucket.org/cdevienne/toxgen (pypi soon hopefully) and +# modified slightly to work in python 2.6 and set some values that are not +# being set. +# +# TODO(harlowja): remove me when toxgen is a pypi package. + +""" +Produce a tox.ini file from a template config file. + +The template config file is a standard tox.ini file with additional sections. +Theses sections will be combined to create new testenv: sections if they do +not exists yet. +""" + +import collections +import itertools +import optparse +import os + +import six + +from six.moves import configparser + +try: + from collections import OrderedDict +except ImportError: + from ordereddict import OrderedDict + + +HEADER = '# DO NOT EDIT THIS FILE - it is machine generated from %(filename)s' +SKIP_VENVS = frozenset(['venv']) + +parser = optparse.OptionParser(epilog=__doc__) +parser.add_option('-i', '--input', dest='input', + default='tox-tmpl.ini', metavar='FILE') +parser.add_option('-o', '--output', dest='output', + default='tox.ini', metavar='FILE') + + +class AxisItem(object): + def __init__(self, axis, name, config): + self.axis = axis + self.isdefault = name[-1] == '*' + self.name = name[:-1] if self.isdefault else name + self.load(config) + + def load(self, config): + sectionname = 'axis:%s:%s' % (self.axis.name, self.name) + if config.has_section(sectionname): + self.options = dict(config.items(sectionname)) + else: + self.options = dict() + + for name, value in self.axis.defaults.items(): + if name not in self.options: + self.options[name] = value + + +class Axis(object): + def __init__(self, name, config): + self.name = name + self.load(config) + + def load(self, config): + self.items = dict() + values = config.get('axes', self.name).split(',') + if config.has_section('axis:%s' % self.name): + self.defaults = dict( + config.items('axis:%s' % self.name) + ) + else: + self.defaults = {} + for value in values: + self.items[value.strip('*')] = AxisItem(self, value, config) + + +def format_list(contents, max_len=80, sep=","): + lines = [] + for line in contents: + if not lines: + lines.append(line + ",") + else: + last_len = len(lines[-1]) + if last_len + len(line) >= max_len: + lines.append(str(line) + sep) + else: + lines[-1] = lines[-1] + str(line) + sep + return "\n".join(lines).rstrip(",") + + +def render(incfg, filename, adjust_envlist=True): + test_envs = set() + for s in incfg.sections(): + if s.startswith("testenv:"): + env = s[len("testenv:"):].strip() + if env in SKIP_VENVS or not env: + continue + test_envs.add(env) + test_envs = [s for s in test_envs if s] + + try: + envlist = incfg.get("tox", 'envlist') + envlist = [e.strip() for e in envlist.split(",")] + envlist = set([e for e in envlist if e]) + except (configparser.NoOptionError, configparser.NoSectionError): + envlist = set() + for e in test_envs: + if e not in envlist: + envlist.add(e) + + if not incfg.has_section("tox"): + incfg.add_section("tox") + incfg.set("tox", "envlist", + format_list(list(sorted(envlist)), max_len=-1)) + + text = six.StringIO() + incfg.write(text) + contents = [ + HEADER % {'filename': os.path.basename(filename)}, + '', + # Remove how configparser uses tabs instead of spaces, madness... + text.getvalue().replace("\t", " " * 4), + ] + return "\n".join(contents) + + +def compile_template(incfg): + axes = dict() + + if incfg.has_section('axes'): + for axis in incfg.options('axes'): + axes[axis] = Axis(axis, incfg) + + out = configparser.ConfigParser(dict_type=OrderedDict) + for section in incfg.sections(): + if section == 'axes' or section.startswith('axis:'): + continue + out.add_section(section) + for name, value in incfg.items(section): + out.set(section, name, value) + + items = [axis.items.keys() for axis in axes.values()] + for combination in itertools.product(*items): + options = {} + + section_name = ( + 'testenv:' + '-'.join([item for item in combination if item]) + ) + section_alt_name = ( + 'testenv:' + '-'.join([ + itemname + for axis, itemname in zip(axes.values(), combination) + if itemname and not axis.items[itemname].isdefault + ]) + ) + if section_alt_name == section_name: + section_alt_name = None + + axes_items = [ + '%s:%s' % (axis, itemname) + for axis, itemname in zip(axes, combination) + ] + + for axis, itemname in zip(axes.values(), combination): + axis_options = axis.items[itemname].options + if 'constraints' in axis_options: + constraints = axis_options['constraints'].split('\n') + for c in constraints: + if c.startswith('!') and c[1:] in axes_items: + continue + for name, value in axis_options.items(): + if name in options: + options[name] += value + else: + options[name] = value + + constraints = options.pop('constraints', '').split('\n') + neg_constraints = [c[1:] for c in constraints if c and c[0] == '!'] + if not set(neg_constraints).isdisjoint(axes_items): + continue + + if not out.has_section(section_name): + out.add_section(section_name) + + if (section_alt_name and not out.has_section(section_alt_name)): + out.add_section(section_alt_name) + + for name, value in reversed(options.items()): + if not out.has_option(section_name, name): + out.set(section_name, name, value) + if section_alt_name and not out.has_option(section_alt_name, name): + out.set(section_alt_name, name, value) + + return out + + +def main(): + options, args = parser.parse_args() + tmpl = configparser.ConfigParser() + with open(options.input, 'rb') as fh: + tmpl.readfp(fh, filename=options.input) + with open(options.output, 'wb') as outfile: + text = render(compile_template(tmpl), options.input) + outfile.write(text) + + +if __name__ == '__main__': + main() + -- cgit v1.2.1