blob: eaf5440474e294d3bfec2cbfe902e3aa2d58aa85 [file]
#!/usr/bin/env python
# Copyright 2014 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
import cStringIO
import logging
import os
import sys
import tempfile
import unittest
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(
__file__.decode(sys.getfilesystemencoding()))))
sys.path.insert(0, ROOT_DIR)
sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party'))
import isolate_format
from depot_tools import auto_stub
from depot_tools import fix_encoding
from utils import file_path
# Access to a protected member XXX of a client class
# pylint: disable=W0212
FAKE_DIR = (
u'z:\\path\\to\\non_existing'
if sys.platform == 'win32' else u'/path/to/non_existing')
class IsolateFormatTest(auto_stub.TestCase):
def test_unknown_key(self):
try:
isolate_format.verify_variables({'foo': [],})
self.fail()
except AssertionError:
pass
def test_unknown_var(self):
try:
isolate_format.verify_condition({'variables': {'foo': [],}}, {})
self.fail()
except AssertionError:
pass
def test_eval_content(self):
try:
# Intrinsics are not available.
isolate_format.eval_content('map(str, [1, 2])')
self.fail()
except NameError:
pass
def test_load_isolate_as_config_empty(self):
expected = {
(): {
'isolate_dir': FAKE_DIR,
},
}
self.assertEqual(
expected,
isolate_format.load_isolate_as_config(FAKE_DIR, {}, None).flatten())
def test_load_isolate_as_config(self):
value = {
'conditions': [
['OS=="amiga" or OS=="atari" or OS=="coleco" or OS=="dendy"', {
'variables': {
'files': ['a', 'b', 'touched'],
},
}],
['OS=="atari"', {
'variables': {
'files': ['c', 'd', 'touched_a', 'x'],
'command': ['echo', 'Hello World'],
'read_only': 2,
},
}],
['OS=="amiga" or OS=="coleco" or OS=="dendy"', {
'variables': {
'files': ['e', 'f', 'touched_e', 'x'],
'command': ['echo', 'You should get an Atari'],
},
}],
['OS=="amiga"', {
'variables': {
'files': ['g'],
'read_only': 1,
},
}],
['OS=="amiga" or OS=="atari" or OS=="dendy"', {
'variables': {
'files': ['h'],
},
}],
],
}
expected = {
(None,): {
'isolate_dir': FAKE_DIR,
},
('amiga',): {
'files': ['a', 'b', 'e', 'f', 'g', 'h', 'touched', 'touched_e', 'x'],
'command': ['echo', 'You should get an Atari'],
'isolate_dir': FAKE_DIR,
'read_only': 1,
},
('atari',): {
'files': ['a', 'b', 'c', 'd', 'h', 'touched', 'touched_a', 'x'],
'command': ['echo', 'Hello World'],
'isolate_dir': FAKE_DIR,
'read_only': 2,
},
('coleco',): {
'files': ['a', 'b', 'e', 'f', 'touched', 'touched_e', 'x'],
'command': ['echo', 'You should get an Atari'],
'isolate_dir': FAKE_DIR,
},
('dendy',): {
'files': ['a', 'b', 'e', 'f', 'h', 'touched', 'touched_e', 'x'],
'command': ['echo', 'You should get an Atari'],
'isolate_dir': FAKE_DIR,
},
}
self.assertEqual(
expected, isolate_format.load_isolate_as_config(
FAKE_DIR, value, None).flatten())
def test_load_isolate_as_config_duplicate_command(self):
value = {
'variables': {
'command': ['rm', '-rf', '/'],
},
'conditions': [
['OS=="atari"', {
'variables': {
'command': ['echo', 'Hello World'],
},
}],
],
}
try:
isolate_format.load_isolate_as_config(FAKE_DIR, value, None)
self.fail()
except AssertionError:
pass
def test_load_isolate_as_config_no_variable(self):
value = {
'variables': {
'command': ['echo', 'You should get an Atari'],
'files': ['a', 'b', 'touched'],
'read_only': 1,
},
}
# The key is the empty tuple, since there is no variable to bind to.
expected = {
(): {
'command': ['echo', 'You should get an Atari'],
'files': ['a', 'b', 'touched'],
'isolate_dir': FAKE_DIR,
'read_only': 1,
},
}
self.assertEqual(
expected, isolate_format.load_isolate_as_config(
FAKE_DIR, value, None).flatten())
def test_merge_two_empty(self):
# Flat stay flat. Pylint is confused about union() return type.
# pylint: disable=E1103
actual = isolate_format.Configs(None, ()).union(
isolate_format.load_isolate_as_config(FAKE_DIR, {}, None)).union(
isolate_format.load_isolate_as_config(FAKE_DIR, {}, None))
expected = {
(): {
'isolate_dir': FAKE_DIR,
},
}
self.assertEqual(expected, actual.flatten())
def test_load_two_conditions(self):
linux = {
'conditions': [
['OS=="linux"', {
'variables': {
'files': [
'file_linux',
'file_common',
],
},
}],
],
}
mac = {
'conditions': [
['OS=="mac"', {
'variables': {
'files': [
'file_mac',
'file_common',
],
},
}],
],
}
expected = {
(None,): {
'isolate_dir': FAKE_DIR,
},
('linux',): {
'files': ['file_common', 'file_linux'],
'isolate_dir': FAKE_DIR,
},
('mac',): {
'files': ['file_common', 'file_mac'],
'isolate_dir': FAKE_DIR,
},
}
# Pylint is confused about union() return type.
# pylint: disable=E1103
configs = isolate_format.Configs(None, ()).union(
isolate_format.load_isolate_as_config(FAKE_DIR, linux, None)).union(
isolate_format.load_isolate_as_config(FAKE_DIR, mac, None)
).flatten()
self.assertEqual(expected, configs)
def test_load_three_conditions(self):
linux = {
'conditions': [
['OS=="linux" and chromeos==1', {
'variables': {
'files': [
'file_linux',
'file_common',
],
},
}],
],
}
mac = {
'conditions': [
['OS=="mac" and chromeos==0', {
'variables': {
'files': [
'file_mac',
'file_common',
],
},
}],
],
}
win = {
'conditions': [
['OS=="win" and chromeos==0', {
'variables': {
'files': [
'file_win',
'file_common',
],
},
}],
],
}
expected = {
(None, None): {
'isolate_dir': FAKE_DIR,
},
('linux', 1): {
'files': ['file_common', 'file_linux'],
'isolate_dir': FAKE_DIR,
},
('mac', 0): {
'files': ['file_common', 'file_mac'],
'isolate_dir': FAKE_DIR,
},
('win', 0): {
'files': ['file_common', 'file_win'],
'isolate_dir': FAKE_DIR,
},
}
# Pylint is confused about union() return type.
# pylint: disable=E1103
configs = isolate_format.Configs(None, ()).union(
isolate_format.load_isolate_as_config(FAKE_DIR, linux, None)).union(
isolate_format.load_isolate_as_config(FAKE_DIR, mac, None)).union(
isolate_format.load_isolate_as_config(FAKE_DIR, win, None))
self.assertEqual(expected, configs.flatten())
def test_safe_index(self):
self.assertEqual(1, isolate_format._safe_index(('a', 'b'), 'b'))
self.assertEqual(None, isolate_format._safe_index(('a', 'b'), 'c'))
def test_get_map_keys(self):
self.assertEqual(
(0, None, 1), isolate_format._get_map_keys(('a', 'b', 'c'), ('a', 'c')))
def test_map_keys(self):
self.assertEqual(
('a', None, 'c'),
isolate_format._map_keys((0, None, 1), ('a', 'c')))
def test_load_multi_variables(self):
# Load an .isolate with different condition on different variables.
data = {
'conditions': [
['OS=="abc"', {
'variables': {
'command': ['bar'],
},
}],
['CHROMEOS=="1"', {
'variables': {
'command': ['foo'],
},
}],
],
}
configs = isolate_format.load_isolate_as_config(FAKE_DIR, data, None)
self.assertEqual(('CHROMEOS', 'OS'), configs.config_variables)
flatten = dict((k, v.flatten()) for k, v in configs._by_config.iteritems())
expected = {
(None, None): {
'isolate_dir': FAKE_DIR,
},
(None, 'abc'): {
'command': ['bar'],
'isolate_dir': FAKE_DIR,
},
('1', None): {
'command': ['foo'],
'isolate_dir': FAKE_DIR,
},
# TODO(maruel): It is a conflict.
('1', 'abc'): {
'command': ['bar'],
'isolate_dir': FAKE_DIR,
},
}
self.assertEqual(expected, flatten)
def test_union_multi_variables(self):
data1 = {
'conditions': [
['OS=="abc"', {
'variables': {
'command': ['bar'],
},
}],
],
}
data2 = {
'conditions': [
['CHROMEOS=="1"', {
'variables': {
'command': ['foo'],
},
}],
],
}
configs1 = isolate_format.load_isolate_as_config(FAKE_DIR, data1, None)
configs2 = isolate_format.load_isolate_as_config(FAKE_DIR, data2, None)
configs = configs1.union(configs2)
self.assertEqual(('CHROMEOS', 'OS'), configs.config_variables)
flatten = dict((k, v.flatten()) for k, v in configs._by_config.iteritems())
expected = {
(None, None): {
'isolate_dir': FAKE_DIR,
},
(None, 'abc'): {
'command': ['bar'],
'isolate_dir': FAKE_DIR,
},
('1', None): {
'command': ['foo'],
'isolate_dir': FAKE_DIR,
},
}
self.assertEqual(expected, flatten)
def test_ConfigSettings_union(self):
lhs_values = {}
rhs_values = {'files': ['data/', 'test/data/']}
lhs = isolate_format.ConfigSettings(lhs_values, '/src/net/third_party/nss')
rhs = isolate_format.ConfigSettings(rhs_values, '/src/base')
out = lhs.union(rhs)
expected = {
'files': ['data/', 'test/data/'],
'isolate_dir': '/src/base',
}
self.assertEqual(expected, out.flatten())
def test_configs_comment(self):
# Pylint is confused with isolate_format.union() return type.
# pylint: disable=E1103
configs = isolate_format.load_isolate_as_config(
FAKE_DIR, {}, '# Yo dawg!\n# Chill out.\n').union(
isolate_format.load_isolate_as_config(FAKE_DIR, {}, None))
self.assertEqual('# Yo dawg!\n# Chill out.\n', configs.file_comment)
configs = isolate_format.load_isolate_as_config(FAKE_DIR, {}, None).union(
isolate_format.load_isolate_as_config(
FAKE_DIR, {}, '# Yo dawg!\n# Chill out.\n'))
self.assertEqual('# Yo dawg!\n# Chill out.\n', configs.file_comment)
# Only keep the first one.
configs = isolate_format.load_isolate_as_config(
FAKE_DIR, {}, '# Yo dawg!\n').union(
isolate_format.load_isolate_as_config(
FAKE_DIR, {}, '# Chill out.\n'))
self.assertEqual('# Yo dawg!\n', configs.file_comment)
def test_extract_comment(self):
self.assertEqual(
'# Foo\n# Bar\n', isolate_format.extract_comment('# Foo\n# Bar\n{}'))
self.assertEqual('', isolate_format.extract_comment('{}'))
def _test_pretty_print_impl(self, value, expected):
actual = cStringIO.StringIO()
isolate_format.pretty_print(value, actual)
self.assertEqual(expected.splitlines(), actual.getvalue().splitlines())
def test_pretty_print_empty(self):
self._test_pretty_print_impl({}, '{\n}\n')
def test_pretty_print_mid_size(self):
value = {
'variables': {
'files': [
'file1',
'file2',
],
},
'conditions': [
['OS==\"foo\"', {
'variables': {
'files': [
'dir1/',
'dir2/',
'file3',
'file4',
],
'command': ['python', '-c', 'print "H\\i\'"'],
'read_only': 2,
},
}],
['OS==\"bar\"', {
'variables': {},
}],
],
}
isolate_format.verify_root(value, {})
# This is an .isolate format.
expected = (
"{\n"
" 'variables': {\n"
" 'files': [\n"
" 'file1',\n"
" 'file2',\n"
" ],\n"
" },\n"
" 'conditions': [\n"
" ['OS==\"foo\"', {\n"
" 'variables': {\n"
" 'command': [\n"
" 'python',\n"
" '-c',\n"
" 'print \"H\\i\'\"',\n"
" ],\n"
" 'files': [\n"
" 'dir1/',\n"
" 'dir2/',\n"
" 'file3',\n"
" 'file4',\n"
" ],\n"
" 'read_only': 2,\n"
" },\n"
" }],\n"
" ['OS==\"bar\"', {\n"
" 'variables': {\n"
" },\n"
" }],\n"
" ],\n"
"}\n")
self._test_pretty_print_impl(value, expected)
def test_convert_old_to_new_else(self):
isolate_with_else_clauses = {
'conditions': [
['OS=="mac"', {
'variables': {'foo': 'bar'},
}, {
'variables': {'x': 'y'},
}],
],
}
with self.assertRaises(isolate_format.IsolateError):
isolate_format.load_isolate_as_config(
FAKE_DIR, isolate_with_else_clauses, None)
def test_match_configs(self):
expectations = [
(
('OS=="win"', ('OS',), [('win',), ('mac',), ('linux',)]),
[('win',)],
),
(
(
'(foo==1 or foo==2) and bar=="b"',
['foo', 'bar'],
[(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')],
),
[(1, 'b'), (2, 'b')],
),
(
(
'bar=="b"',
['foo', 'bar'],
[(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')],
),
# TODO(maruel): When a free variable match is found, it should not
# list all the bounded values in addition. The problem is when an
# intersection of two different bound variables that are tested singly
# in two different conditions.
[(1, 'b'), (2, 'b'), (None, 'b')],
),
(
(
'foo==1 or bar=="b"',
['foo', 'bar'],
[(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')],
),
# TODO(maruel): (None, 'b') would match.
# It is hard in this case to realize that each of the variables 'foo'
# and 'bar' can be unbounded in a specific case.
[(1, 'a'), (1, 'b'), (2, 'b'), (1, None)],
),
]
for data, expected in expectations:
self.assertEqual(expected, isolate_format.match_configs(*data))
def test_load_with_globals(self):
values = {
'variables': {
'files': [
'file_common',
],
},
'conditions': [
['OS=="linux"', {
'variables': {
'files': [
'file_linux',
],
'read_only': 1,
},
}],
['OS=="mac" or OS=="win"', {
'variables': {
'files': [
'file_non_linux',
],
'read_only': 0,
},
}],
],
}
expected = {
(None,): {
'files': [
'file_common',
],
'isolate_dir': FAKE_DIR,
},
('linux',): {
'files': [
'file_linux',
],
'isolate_dir': FAKE_DIR,
'read_only': 1,
},
('mac',): {
'files': [
'file_non_linux',
],
'isolate_dir': FAKE_DIR,
'read_only': 0,
},
('win',): {
'files': [
'file_non_linux',
],
'isolate_dir': FAKE_DIR,
'read_only': 0,
},
}
actual = isolate_format.load_isolate_as_config(FAKE_DIR, values, None)
self.assertEqual(expected, actual.flatten())
def test_and_or_bug(self):
a = {
'conditions': [
['use_x11==0', {
'variables': {
'command': ['foo', 'x11=0'],
},
}],
['OS=="linux" and chromeos==0', {
'variables': {
'command': ['foo', 'linux'],
},
}],
],
}
def load_included_isolate(isolate_dir, _isolate_path):
return isolate_format.load_isolate_as_config(isolate_dir, a, None)
self.mock(isolate_format, 'load_included_isolate', load_included_isolate)
b = {
'conditions': [
['use_x11==1', {
'variables': {
'command': ['foo', 'x11=1'],
},
}],
],
'includes': [
'a',
],
}
variables = {'use_x11': 1, 'OS': 'linux', 'chromeos': 0}
config = isolate_format.load_isolate_for_config('/', str(b), variables)
self.assertEqual((['foo', 'x11=1'], [], None, '/'), config)
variables = {'use_x11': 0, 'OS': 'linux', 'chromeos': 0}
config = isolate_format.load_isolate_for_config('/', str(b), variables)
self.assertEqual(([], [], None, '/'), config)
class IsolateFormatTmpDirTest(unittest.TestCase):
def setUp(self):
super(IsolateFormatTmpDirTest, self).setUp()
self.tempdir = tempfile.mkdtemp(prefix=u'isolate_')
def tearDown(self):
try:
file_path.rmtree(self.tempdir)
finally:
super(IsolateFormatTmpDirTest, self).tearDown()
def test_load_with_includes(self):
included_isolate = {
'variables': {
'files': [
'file_common',
],
},
'conditions': [
['OS=="linux"', {
'variables': {
'files': [
'file_linux',
],
'read_only': 1,
},
}],
['OS=="mac" or OS=="win"', {
'variables': {
'files': [
'file_non_linux',
],
'read_only': 0,
},
}],
],
}
with open(os.path.join(self.tempdir, 'included.isolate'), 'wb') as f:
isolate_format.pretty_print(included_isolate, f)
values = {
'includes': ['included.isolate'],
'variables': {
'files': [
'file_less_common',
],
},
'conditions': [
['OS=="mac"', {
'variables': {
'files': [
'file_mac',
],
'read_only': 2,
},
}],
],
}
actual = isolate_format.load_isolate_as_config(self.tempdir, values, None)
expected = {
(None,): {
'files': [
'file_common',
'file_less_common',
],
'isolate_dir': self.tempdir,
},
('linux',): {
'files': [
'file_linux',
],
'isolate_dir': self.tempdir,
'read_only': 1,
},
('mac',): {
'files': [
'file_mac',
'file_non_linux',
],
'isolate_dir': self.tempdir,
'read_only': 2,
},
('win',): {
'files': [
'file_non_linux',
],
'isolate_dir': self.tempdir,
'read_only': 0,
},
}
self.assertEqual(expected, actual.flatten())
def test_load_with_includes_with_commands(self):
# This one is messy. Check that isolate_dir is the expected value. To
# achieve this, put the .isolate files into subdirectories.
dir_1 = os.path.join(self.tempdir, '1')
dir_3 = os.path.join(self.tempdir, '3')
dir_3_2 = os.path.join(self.tempdir, '3', '2')
os.mkdir(dir_1)
os.mkdir(dir_3)
os.mkdir(dir_3_2)
isolate1 = {
'conditions': [
['OS=="amiga" or OS=="win"', {
'variables': {
'command': [
'foo', 'amiga_or_win',
],
},
}],
['OS=="linux"', {
'variables': {
'command': [
'foo', 'linux',
],
'files': [
'file_linux',
],
},
}],
['OS=="mac" or OS=="win"', {
'variables': {
'files': [
'file_non_linux',
],
},
}],
],
}
isolate2 = {
'conditions': [
['OS=="linux" or OS=="mac"', {
'variables': {
'command': [
'foo', 'linux_or_mac',
],
'files': [
'other/file',
],
},
}],
],
}
# Do not define command in isolate3, otherwise commands in the other
# included .isolated will be ignored.
isolate3 = {
'includes': [
'../1/isolate1.isolate',
'2/isolate2.isolate',
],
'conditions': [
['OS=="amiga"', {
'variables': {
'files': [
'file_amiga',
],
},
}],
['OS=="mac"', {
'variables': {
'files': [
'file_mac',
],
},
}],
],
}
# No need to write isolate3.
with open(os.path.join(dir_1, 'isolate1.isolate'), 'wb') as f:
isolate_format.pretty_print(isolate1, f)
with open(os.path.join(dir_3_2, 'isolate2.isolate'), 'wb') as f:
isolate_format.pretty_print(isolate2, f)
# The 'isolate_dir' are important, they are what will be used when
# definining the final isolate_dir to use to run the command in the
# .isolated file.
actual = isolate_format.load_isolate_as_config(dir_3, isolate3, None)
expected = {
(None,): {
# TODO(maruel): See TODO in ConfigSettings.flatten().
# TODO(maruel): If kept, in this case dir_3 should be selected.
'isolate_dir': dir_1,
},
('amiga',): {
'command': ['foo', 'amiga_or_win'],
'files': [
# Note that the file was rebased from isolate1. This is important,
# isolate1 represent the canonical root path because it is the one
# that defined the command.
'../3/file_amiga',
],
'isolate_dir': dir_1,
},
('linux',): {
# Last included takes precedence. *command comes from isolate2*, so
# it becomes the canonical root, so reference to file from isolate1 is
# via '../../1'.
'command': ['foo', 'linux_or_mac'],
'files': [
'../../1/file_linux',
'other/file',
],
'isolate_dir': dir_3_2,
},
('mac',): {
'command': ['foo', 'linux_or_mac'],
'files': [
'../../1/file_non_linux',
'../file_mac',
'other/file',
],
'isolate_dir': dir_3_2,
},
('win',): {
# command comes from isolate1.
'command': ['foo', 'amiga_or_win'],
'files': [
# While this may be surprising, this is because the command was
# defined in isolate1, not isolate3.
'file_non_linux',
],
'isolate_dir': dir_1,
},
}
self.assertEqual(expected, actual.flatten())
def test_load_with_includes_with_commands_and_variables(self):
# This one is the pinacle of fun. Check that isolate_dir is the expected
# value. To achieve this, put the .isolate files into subdirectories.
dir_1 = os.path.join(self.tempdir, '1')
dir_3 = os.path.join(self.tempdir, '3')
dir_3_2 = os.path.join(self.tempdir, '3', '2')
os.mkdir(dir_1)
os.mkdir(dir_3)
os.mkdir(dir_3_2)
isolate1 = {
'conditions': [
['OS=="amiga" or OS=="win"', {
'variables': {
'command': [
'foo', 'amiga_or_win', '<(PATH)',
],
},
}],
['OS=="linux"', {
'variables': {
'command': [
'foo', 'linux', '<(PATH)',
],
'files': [
'<(PATH)/file_linux',
],
},
}],
['OS=="mac" or OS=="win"', {
'variables': {
'files': [
'<(PATH)/file_non_linux',
],
},
}],
],
}
isolate2 = {
'conditions': [
['OS=="linux" or OS=="mac"', {
'variables': {
'command': [
'foo', 'linux_or_mac', '<(PATH)',
],
'files': [
'<(PATH)/other/file',
],
},
}],
],
}
# Do not define command in isolate3, otherwise commands in the other
# included .isolated will be ignored.
isolate3 = {
'includes': [
'../1/isolate1.isolate',
'2/isolate2.isolate',
],
'conditions': [
['OS=="amiga"', {
'variables': {
'files': [
'<(PATH)/file_amiga',
],
},
}],
['OS=="mac"', {
'variables': {
'files': [
'<(PATH)/file_mac',
],
},
}],
],
}
# No need to write isolate3.
with open(os.path.join(dir_1, 'isolate1.isolate'), 'wb') as f:
isolate_format.pretty_print(isolate1, f)
with open(os.path.join(dir_3_2, 'isolate2.isolate'), 'wb') as f:
isolate_format.pretty_print(isolate2, f)
# The 'isolate_dir' are important, they are what will be used when
# definining the final isolate_dir to use to run the command in the
# .isolated file.
actual = isolate_format.load_isolate_as_config(dir_3, isolate3, None)
expected = {
(None,): {
'isolate_dir': dir_1,
},
('amiga',): {
'command': ['foo', 'amiga_or_win', '<(PATH)'],
'files': [
'<(PATH)/file_amiga',
],
'isolate_dir': dir_1,
},
('linux',): {
# Last included takes precedence. *command comes from isolate2*, so
# it becomes the canonical root, so reference to file from isolate1 is
# via '../../1'.
'command': ['foo', 'linux_or_mac', '<(PATH)'],
'files': [
'<(PATH)/file_linux',
'<(PATH)/other/file',
],
'isolate_dir': dir_3_2,
},
('mac',): {
'command': ['foo', 'linux_or_mac', '<(PATH)'],
'files': [
'<(PATH)/file_mac',
'<(PATH)/file_non_linux',
'<(PATH)/other/file',
],
'isolate_dir': dir_3_2,
},
('win',): {
# command comes from isolate1.
'command': ['foo', 'amiga_or_win', '<(PATH)'],
'files': [
'<(PATH)/file_non_linux',
],
'isolate_dir': dir_1,
},
}
self.assertEqual(expected, actual.flatten())
if __name__ == '__main__':
fix_encoding.fix_encoding()
logging.basicConfig(
level=logging.DEBUG if '-v' in sys.argv else logging.ERROR,
format='%(levelname)5s %(filename)15s(%(lineno)3d): %(message)s')
if '-v' in sys.argv:
unittest.TestCase.maxDiff = None
unittest.main()