1# Copyright (C) 2010 Google Inc. All rights reserved.
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions are
5# met:
6#
7#    * Redistributions of source code must retain the above copyright
8# notice, this list of conditions and the following disclaimer.
9#    * Redistributions in binary form must reproduce the above
10# copyright notice, this list of conditions and the following disclaimer
11# in the documentation and/or other materials provided with the
12# distribution.
13#    * Neither the name of Google Inc. nor the names of its
14# contributors may be used to endorse or promote products derived from
15# this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29import logging
30import optparse
31import sys
32import tempfile
33import unittest
34
35from webkitpy.common.system.executive import Executive, ScriptError
36from webkitpy.common.system import executive_mock
37from webkitpy.common.system.filesystem_mock import MockFileSystem
38from webkitpy.common.system.outputcapture import OutputCapture
39from webkitpy.common.system.path import abspath_to_uri
40from webkitpy.tool.mocktool import MockOptions
41from webkitpy.common.system.executive_mock import MockExecutive, MockExecutive2
42from webkitpy.common.system.systemhost import SystemHost
43from webkitpy.common.system.systemhost_mock import MockSystemHost
44
45from webkitpy.layout_tests.port import Port, Driver, DriverOutput
46from webkitpy.layout_tests.port.base import VirtualTestSuite
47from webkitpy.layout_tests.port.test import add_unit_tests_to_mock_filesystem, TestPort
48
49class PortTest(unittest.TestCase):
50    def make_port(self, executive=None, with_tests=False, port_name=None, **kwargs):
51        host = MockSystemHost()
52        if executive:
53            host.executive = executive
54        if with_tests:
55            add_unit_tests_to_mock_filesystem(host.filesystem)
56            return TestPort(host, **kwargs)
57        return Port(host, port_name or 'baseport', **kwargs)
58
59    def test_default_child_processes(self):
60        port = self.make_port()
61        self.assertIsNotNone(port.default_child_processes())
62
63    def test_format_wdiff_output_as_html(self):
64        output = "OUTPUT %s %s %s" % (Port._WDIFF_DEL, Port._WDIFF_ADD, Port._WDIFF_END)
65        html = self.make_port()._format_wdiff_output_as_html(output)
66        expected_html = "<head><style>.del { background: #faa; } .add { background: #afa; }</style></head><pre>OUTPUT <span class=del> <span class=add> </span></pre>"
67        self.assertEqual(html, expected_html)
68
69    def test_wdiff_command(self):
70        port = self.make_port()
71        port._path_to_wdiff = lambda: "/path/to/wdiff"
72        command = port._wdiff_command("/actual/path", "/expected/path")
73        expected_command = [
74            "/path/to/wdiff",
75            "--start-delete=##WDIFF_DEL##",
76            "--end-delete=##WDIFF_END##",
77            "--start-insert=##WDIFF_ADD##",
78            "--end-insert=##WDIFF_END##",
79            "/actual/path",
80            "/expected/path",
81        ]
82        self.assertEqual(command, expected_command)
83
84    def _file_with_contents(self, contents, encoding="utf-8"):
85        new_file = tempfile.NamedTemporaryFile()
86        new_file.write(contents.encode(encoding))
87        new_file.flush()
88        return new_file
89
90    def test_pretty_patch_os_error(self):
91        port = self.make_port(executive=executive_mock.MockExecutive2(exception=OSError))
92        oc = OutputCapture()
93        oc.capture_output()
94        self.assertEqual(port.pretty_patch_text("patch.txt"),
95                         port._pretty_patch_error_html)
96
97        # This tests repeated calls to make sure we cache the result.
98        self.assertEqual(port.pretty_patch_text("patch.txt"),
99                         port._pretty_patch_error_html)
100        oc.restore_output()
101
102    def test_pretty_patch_script_error(self):
103        # FIXME: This is some ugly white-box test hacking ...
104        port = self.make_port(executive=executive_mock.MockExecutive2(exception=ScriptError))
105        port._pretty_patch_available = True
106        self.assertEqual(port.pretty_patch_text("patch.txt"),
107                         port._pretty_patch_error_html)
108
109        # This tests repeated calls to make sure we cache the result.
110        self.assertEqual(port.pretty_patch_text("patch.txt"),
111                         port._pretty_patch_error_html)
112
113    def test_wdiff_text(self):
114        port = self.make_port()
115        port.wdiff_available = lambda: True
116        port._run_wdiff = lambda a, b: 'PASS'
117        self.assertEqual('PASS', port.wdiff_text(None, None))
118
119    def test_diff_text(self):
120        port = self.make_port()
121        # Make sure that we don't run into decoding exceptions when the
122        # filenames are unicode, with regular or malformed input (expected or
123        # actual input is always raw bytes, not unicode).
124        port.diff_text('exp', 'act', 'exp.txt', 'act.txt')
125        port.diff_text('exp', 'act', u'exp.txt', 'act.txt')
126        port.diff_text('exp', 'act', u'a\xac\u1234\u20ac\U00008000', 'act.txt')
127
128        port.diff_text('exp' + chr(255), 'act', 'exp.txt', 'act.txt')
129        port.diff_text('exp' + chr(255), 'act', u'exp.txt', 'act.txt')
130
131        # Though expected and actual files should always be read in with no
132        # encoding (and be stored as str objects), test unicode inputs just to
133        # be safe.
134        port.diff_text(u'exp', 'act', 'exp.txt', 'act.txt')
135        port.diff_text(
136            u'a\xac\u1234\u20ac\U00008000', 'act', 'exp.txt', 'act.txt')
137
138        # And make sure we actually get diff output.
139        diff = port.diff_text('foo', 'bar', 'exp.txt', 'act.txt')
140        self.assertIn('foo', diff)
141        self.assertIn('bar', diff)
142        self.assertIn('exp.txt', diff)
143        self.assertIn('act.txt', diff)
144        self.assertNotIn('nosuchthing', diff)
145
146        # Test for missing newline at end of file diff output.
147        content_a = "Hello\n\nWorld"
148        content_b = "Hello\n\nWorld\n\n\n"
149        expected = "--- exp.txt\n+++ act.txt\n@@ -1,3 +1,5 @@\n Hello\n \n-World\n\ No newline at end of file\n+World\n+\n+\n"
150        self.assertEqual(expected, port.diff_text(content_a, content_b, 'exp.txt', 'act.txt'))
151
152    def test_setup_test_run(self):
153        port = self.make_port()
154        # This routine is a no-op. We just test it for coverage.
155        port.setup_test_run()
156
157    def test_test_dirs(self):
158        port = self.make_port()
159        port.host.filesystem.write_text_file(port.layout_tests_dir() + '/canvas/test', '')
160        port.host.filesystem.write_text_file(port.layout_tests_dir() + '/css2.1/test', '')
161        dirs = port.test_dirs()
162        self.assertIn('canvas', dirs)
163        self.assertIn('css2.1', dirs)
164
165    def test_skipped_perf_tests(self):
166        port = self.make_port()
167
168        def add_text_file(dirname, filename, content='some content'):
169            dirname = port.host.filesystem.join(port.perf_tests_dir(), dirname)
170            port.host.filesystem.maybe_make_directory(dirname)
171            port.host.filesystem.write_text_file(port.host.filesystem.join(dirname, filename), content)
172
173        add_text_file('inspector', 'test1.html')
174        add_text_file('inspector', 'unsupported_test1.html')
175        add_text_file('inspector', 'test2.html')
176        add_text_file('inspector/resources', 'resource_file.html')
177        add_text_file('unsupported', 'unsupported_test2.html')
178        add_text_file('', 'Skipped', '\n'.join(['Layout', '', 'SunSpider', 'Supported/some-test.html']))
179        self.assertEqual(port.skipped_perf_tests(), ['Layout', 'SunSpider', 'Supported/some-test.html'])
180
181    def test_get_option__set(self):
182        options, args = optparse.OptionParser().parse_args([])
183        options.foo = 'bar'
184        port = self.make_port(options=options)
185        self.assertEqual(port.get_option('foo'), 'bar')
186
187    def test_get_option__unset(self):
188        port = self.make_port()
189        self.assertIsNone(port.get_option('foo'))
190
191    def test_get_option__default(self):
192        port = self.make_port()
193        self.assertEqual(port.get_option('foo', 'bar'), 'bar')
194
195    def test_additional_platform_directory(self):
196        port = self.make_port(port_name='foo')
197        port.default_baseline_search_path = lambda: ['LayoutTests/platform/foo']
198        layout_test_dir = port.layout_tests_dir()
199        test_file = 'fast/test.html'
200
201        # No additional platform directory
202        self.assertEqual(
203            port.expected_baselines(test_file, '.txt'),
204            [(None, 'fast/test-expected.txt')])
205        self.assertEqual(port.baseline_path(), 'LayoutTests/platform/foo')
206
207        # Simple additional platform directory
208        port._options.additional_platform_directory = ['/tmp/local-baselines']
209        port._filesystem.write_text_file('/tmp/local-baselines/fast/test-expected.txt', 'foo')
210        self.assertEqual(
211            port.expected_baselines(test_file, '.txt'),
212            [('/tmp/local-baselines', 'fast/test-expected.txt')])
213        self.assertEqual(port.baseline_path(), '/tmp/local-baselines')
214
215        # Multiple additional platform directories
216        port._options.additional_platform_directory = ['/foo', '/tmp/local-baselines']
217        self.assertEqual(
218            port.expected_baselines(test_file, '.txt'),
219            [('/tmp/local-baselines', 'fast/test-expected.txt')])
220        self.assertEqual(port.baseline_path(), '/foo')
221
222    def test_nonexistant_expectations(self):
223        port = self.make_port(port_name='foo')
224        port.expectations_files = lambda: ['/mock-checkout/third_party/WebKit/LayoutTests/platform/exists/TestExpectations', '/mock-checkout/third_party/WebKit/LayoutTests/platform/nonexistant/TestExpectations']
225        port._filesystem.write_text_file('/mock-checkout/third_party/WebKit/LayoutTests/platform/exists/TestExpectations', '')
226        self.assertEqual('\n'.join(port.expectations_dict().keys()), '/mock-checkout/third_party/WebKit/LayoutTests/platform/exists/TestExpectations')
227
228    def test_additional_expectations(self):
229        port = self.make_port(port_name='foo')
230        port.port_name = 'foo'
231        port._filesystem.write_text_file('/mock-checkout/third_party/WebKit/LayoutTests/platform/foo/TestExpectations', '')
232        port._filesystem.write_text_file(
233            '/tmp/additional-expectations-1.txt', 'content1\n')
234        port._filesystem.write_text_file(
235            '/tmp/additional-expectations-2.txt', 'content2\n')
236
237        self.assertEqual('\n'.join(port.expectations_dict().values()), '')
238
239        port._options.additional_expectations = [
240            '/tmp/additional-expectations-1.txt']
241        self.assertEqual('\n'.join(port.expectations_dict().values()), 'content1\n')
242
243        port._options.additional_expectations = [
244            '/tmp/nonexistent-file', '/tmp/additional-expectations-1.txt']
245        self.assertEqual('\n'.join(port.expectations_dict().values()), 'content1\n')
246
247        port._options.additional_expectations = [
248            '/tmp/additional-expectations-1.txt', '/tmp/additional-expectations-2.txt']
249        self.assertEqual('\n'.join(port.expectations_dict().values()), 'content1\n\ncontent2\n')
250
251    def test_additional_env_var(self):
252        port = self.make_port(options=optparse.Values({'additional_env_var': ['FOO=BAR', 'BAR=FOO']}))
253        self.assertEqual(port.get_option('additional_env_var'), ['FOO=BAR', 'BAR=FOO'])
254        environment = port.setup_environ_for_server()
255        self.assertTrue(('FOO' in environment) & ('BAR' in environment))
256        self.assertEqual(environment['FOO'], 'BAR')
257        self.assertEqual(environment['BAR'], 'FOO')
258
259    def test_find_no_paths_specified(self):
260        port = self.make_port(with_tests=True)
261        layout_tests_dir = port.layout_tests_dir()
262        tests = port.tests([])
263        self.assertNotEqual(len(tests), 0)
264
265    def test_find_one_test(self):
266        port = self.make_port(with_tests=True)
267        tests = port.tests(['failures/expected/image.html'])
268        self.assertEqual(len(tests), 1)
269
270    def test_find_glob(self):
271        port = self.make_port(with_tests=True)
272        tests = port.tests(['failures/expected/im*'])
273        self.assertEqual(len(tests), 2)
274
275    def test_find_with_skipped_directories(self):
276        port = self.make_port(with_tests=True)
277        tests = port.tests(['userscripts'])
278        self.assertNotIn('userscripts/resources/iframe.html', tests)
279
280    def test_find_with_skipped_directories_2(self):
281        port = self.make_port(with_tests=True)
282        tests = port.tests(['userscripts/resources'])
283        self.assertEqual(tests, [])
284
285    def test_is_test_file(self):
286        filesystem = MockFileSystem()
287        self.assertTrue(Port.is_test_file(filesystem, '', 'foo.html'))
288        self.assertTrue(Port.is_test_file(filesystem, '', 'foo.svg'))
289        self.assertTrue(Port.is_test_file(filesystem, '', 'test-ref-test.html'))
290        self.assertFalse(Port.is_test_file(filesystem, '', 'foo.png'))
291        self.assertFalse(Port.is_test_file(filesystem, '', 'foo-expected.html'))
292        self.assertFalse(Port.is_test_file(filesystem, '', 'foo-expected.svg'))
293        self.assertFalse(Port.is_test_file(filesystem, '', 'foo-expected.xht'))
294        self.assertFalse(Port.is_test_file(filesystem, '', 'foo-expected-mismatch.html'))
295        self.assertFalse(Port.is_test_file(filesystem, '', 'foo-expected-mismatch.svg'))
296        self.assertFalse(Port.is_test_file(filesystem, '', 'foo-expected-mismatch.xhtml'))
297        self.assertFalse(Port.is_test_file(filesystem, '', 'foo-ref.html'))
298        self.assertFalse(Port.is_test_file(filesystem, '', 'foo-notref.html'))
299        self.assertFalse(Port.is_test_file(filesystem, '', 'foo-notref.xht'))
300        self.assertFalse(Port.is_test_file(filesystem, '', 'foo-ref.xhtml'))
301        self.assertFalse(Port.is_test_file(filesystem, '', 'ref-foo.html'))
302        self.assertFalse(Port.is_test_file(filesystem, '', 'notref-foo.xhr'))
303
304    def test_parse_reftest_list(self):
305        port = self.make_port(with_tests=True)
306        port.host.filesystem.files['bar/reftest.list'] = "\n".join(["== test.html test-ref.html",
307        "",
308        "# some comment",
309        "!= test-2.html test-notref.html # more comments",
310        "== test-3.html test-ref.html",
311        "== test-3.html test-ref2.html",
312        "!= test-3.html test-notref.html",
313        "fuzzy(80,500) == test-3 test-ref.html"])
314
315        # Note that we don't support the syntax in the last line; the code should ignore it, rather than crashing.
316
317        reftest_list = Port._parse_reftest_list(port.host.filesystem, 'bar')
318        self.assertEqual(reftest_list, {'bar/test.html': [('==', 'bar/test-ref.html')],
319            'bar/test-2.html': [('!=', 'bar/test-notref.html')],
320            'bar/test-3.html': [('==', 'bar/test-ref.html'), ('==', 'bar/test-ref2.html'), ('!=', 'bar/test-notref.html')]})
321
322    def test_reference_files(self):
323        port = self.make_port(with_tests=True)
324        self.assertEqual(port.reference_files('passes/svgreftest.svg'), [('==', port.layout_tests_dir() + '/passes/svgreftest-expected.svg')])
325        self.assertEqual(port.reference_files('passes/xhtreftest.svg'), [('==', port.layout_tests_dir() + '/passes/xhtreftest-expected.html')])
326        self.assertEqual(port.reference_files('passes/phpreftest.php'), [('!=', port.layout_tests_dir() + '/passes/phpreftest-expected-mismatch.svg')])
327
328    def test_operating_system(self):
329        self.assertEqual('mac', self.make_port().operating_system())
330
331    def test_http_server_supports_ipv6(self):
332        port = self.make_port()
333        self.assertTrue(port.http_server_supports_ipv6())
334        port.host.platform.os_name = 'cygwin'
335        self.assertFalse(port.http_server_supports_ipv6())
336        port.host.platform.os_name = 'win'
337        self.assertFalse(port.http_server_supports_ipv6())
338
339    def test_check_httpd_success(self):
340        port = self.make_port(executive=MockExecutive2())
341        port.path_to_apache = lambda: '/usr/sbin/httpd'
342        capture = OutputCapture()
343        capture.capture_output()
344        self.assertTrue(port.check_httpd())
345        _, _, logs = capture.restore_output()
346        self.assertEqual('', logs)
347
348    def test_httpd_returns_error_code(self):
349        port = self.make_port(executive=MockExecutive2(exit_code=1))
350        port.path_to_apache = lambda: '/usr/sbin/httpd'
351        capture = OutputCapture()
352        capture.capture_output()
353        self.assertFalse(port.check_httpd())
354        _, _, logs = capture.restore_output()
355        self.assertEqual('httpd seems broken. Cannot run http tests.\n', logs)
356
357    def test_test_exists(self):
358        port = self.make_port(with_tests=True)
359        self.assertTrue(port.test_exists('passes'))
360        self.assertTrue(port.test_exists('passes/text.html'))
361        self.assertFalse(port.test_exists('passes/does_not_exist.html'))
362
363        self.assertTrue(port.test_exists('virtual'))
364        self.assertFalse(port.test_exists('virtual/does_not_exist.html'))
365        self.assertTrue(port.test_exists('virtual/virtual_passes/passes/text.html'))
366
367    def test_test_isfile(self):
368        port = self.make_port(with_tests=True)
369        self.assertFalse(port.test_isfile('passes'))
370        self.assertTrue(port.test_isfile('passes/text.html'))
371        self.assertFalse(port.test_isfile('passes/does_not_exist.html'))
372
373        self.assertFalse(port.test_isfile('virtual'))
374        self.assertTrue(port.test_isfile('virtual/virtual_passes/passes/text.html'))
375        self.assertFalse(port.test_isfile('virtual/does_not_exist.html'))
376
377    def test_test_isdir(self):
378        port = self.make_port(with_tests=True)
379        self.assertTrue(port.test_isdir('passes'))
380        self.assertFalse(port.test_isdir('passes/text.html'))
381        self.assertFalse(port.test_isdir('passes/does_not_exist.html'))
382        self.assertFalse(port.test_isdir('passes/does_not_exist/'))
383
384        self.assertTrue(port.test_isdir('virtual'))
385        self.assertFalse(port.test_isdir('virtual/does_not_exist.html'))
386        self.assertFalse(port.test_isdir('virtual/does_not_exist/'))
387        self.assertFalse(port.test_isdir('virtual/virtual_passes/passes/text.html'))
388
389    def test_tests(self):
390        port = self.make_port(with_tests=True)
391        tests = port.tests([])
392        self.assertIn('passes/text.html', tests)
393        self.assertIn('virtual/virtual_passes/passes/text.html', tests)
394
395        tests = port.tests(['passes'])
396        self.assertIn('passes/text.html', tests)
397        self.assertIn('passes/virtual_passes/test-virtual-passes.html', tests)
398        self.assertNotIn('virtual/virtual_passes/passes/text.html', tests)
399
400        tests = port.tests(['virtual/virtual_passes/passes'])
401        self.assertNotIn('passes/text.html', tests)
402        self.assertIn('virtual/virtual_passes/passes/test-virtual-passes.html', tests)
403        self.assertNotIn('passes/test-virtual-passes.html', tests)
404        self.assertNotIn('virtual/virtual_passes/passes/test-virtual-virtual/passes.html', tests)
405        self.assertNotIn('virtual/virtual_passes/passes/virtual_passes/passes/test-virtual-passes.html', tests)
406
407    def test_build_path(self):
408        port = self.make_port(options=optparse.Values({'build_directory': '/my-build-directory/'}))
409        self.assertEqual(port._build_path(), '/my-build-directory/Release')
410
411    def test_dont_require_http_server(self):
412        port = self.make_port()
413        self.assertEqual(port.requires_http_server(), False)
414
415    def test_can_load_actual_virtual_test_suite_file(self):
416        port = Port(SystemHost(), 'baseport')
417
418        # If this call returns successfully, we found and loaded the LayoutTests/VirtualTestSuites.
419        _ = port.virtual_test_suites()
420
421    def test_good_virtual_test_suite_file(self):
422        port = self.make_port()
423        fs = port._filesystem
424        fs.write_text_file(fs.join(port.layout_tests_dir(), 'VirtualTestSuites'),
425                           '[{"prefix": "bar", "base": "fast/bar", "args": ["--bar"]}]')
426
427        # If this call returns successfully, we found and loaded the LayoutTests/VirtualTestSuites.
428        _ = port.virtual_test_suites()
429
430    def test_virtual_test_suite_file_is_not_json(self):
431        port = self.make_port()
432        fs = port._filesystem
433        fs.write_text_file(fs.join(port.layout_tests_dir(), 'VirtualTestSuites'),
434                           '{[{[')
435        self.assertRaises(ValueError, port.virtual_test_suites)
436
437    def test_missing_virtual_test_suite_file(self):
438        port = self.make_port()
439        self.assertRaises(AssertionError, port.virtual_test_suites)
440
441
442class NaturalCompareTest(unittest.TestCase):
443    def setUp(self):
444        self._port = TestPort(MockSystemHost())
445
446    def assert_cmp(self, x, y, result):
447        self.assertEqual(cmp(self._port._natural_sort_key(x), self._port._natural_sort_key(y)), result)
448
449    def test_natural_compare(self):
450        self.assert_cmp('a', 'a', 0)
451        self.assert_cmp('ab', 'a', 1)
452        self.assert_cmp('a', 'ab', -1)
453        self.assert_cmp('', '', 0)
454        self.assert_cmp('', 'ab', -1)
455        self.assert_cmp('1', '2', -1)
456        self.assert_cmp('2', '1', 1)
457        self.assert_cmp('1', '10', -1)
458        self.assert_cmp('2', '10', -1)
459        self.assert_cmp('foo_1.html', 'foo_2.html', -1)
460        self.assert_cmp('foo_1.1.html', 'foo_2.html', -1)
461        self.assert_cmp('foo_1.html', 'foo_10.html', -1)
462        self.assert_cmp('foo_2.html', 'foo_10.html', -1)
463        self.assert_cmp('foo_23.html', 'foo_10.html', 1)
464        self.assert_cmp('foo_23.html', 'foo_100.html', -1)
465
466
467class KeyCompareTest(unittest.TestCase):
468    def setUp(self):
469        self._port = TestPort(MockSystemHost())
470
471    def assert_cmp(self, x, y, result):
472        self.assertEqual(cmp(self._port.test_key(x), self._port.test_key(y)), result)
473
474    def test_test_key(self):
475        self.assert_cmp('/a', '/a', 0)
476        self.assert_cmp('/a', '/b', -1)
477        self.assert_cmp('/a2', '/a10', -1)
478        self.assert_cmp('/a2/foo', '/a10/foo', -1)
479        self.assert_cmp('/a/foo11', '/a/foo2', 1)
480        self.assert_cmp('/ab', '/a/a/b', -1)
481        self.assert_cmp('/a/a/b', '/ab', 1)
482        self.assert_cmp('/foo-bar/baz', '/foo/baz', -1)
483
484
485class VirtualTestSuiteTest(unittest.TestCase):
486    def test_basic(self):
487        suite = VirtualTestSuite(prefix='suite', base='base/foo', args=['--args'])
488        self.assertEqual(suite.name, 'virtual/suite/base/foo')
489        self.assertEqual(suite.base, 'base/foo')
490        self.assertEqual(suite.args, ['--args'])
491
492    def test_no_slash(self):
493        self.assertRaises(AssertionError, VirtualTestSuite, prefix='suite/bar', base='base/foo', args=['--args'])
494