Merge pull request #4958 from bonzini/tap

Add initial TAP test support
pull/5015/head
Jussi Pakkanen 6 years ago committed by GitHub
commit 1997ac2478
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      docs/markdown/Reference-manual.md
  2. 10
      docs/markdown/Unit-tests.md
  3. 5
      mesonbuild/backend/backends.py
  4. 11
      mesonbuild/interpreter.py
  5. 232
      mesonbuild/mtest.py
  6. 271
      run_unittests.py
  7. 10
      test cases/common/212 tap tests/meson.build
  8. 10
      test cases/common/212 tap tests/tester.c
  9. 3
      test cases/failing test/4 hard error/main.c
  10. 4
      test cases/failing test/4 hard error/meson.build
  11. 6
      test cases/failing test/5 tap tests/meson.build
  12. 10
      test cases/failing test/5 tap tests/tester.c

@ -1406,10 +1406,7 @@ executable to run. The executable can be an [executable build target
object](#build-target-object) returned by object](#build-target-object) returned by
[`executable()`](#executable) or an [external program [`executable()`](#executable) or an [external program
object](#external-program-object) returned by object](#external-program-object) returned by
[`find_program()`](#find_program). The executable's exit code is used [`find_program()`](#find_program).
by the test harness to record the outcome of the test, for example
exit code zero indicates success. For more on the Meson test harness
protocol read [Unit Tests](Unit-tests.md).
Keyword arguments are the following: Keyword arguments are the following:
@ -1446,6 +1443,12 @@ Keyword arguments are the following:
before test is executed even if they have `build_by_default : false`. before test is executed even if they have `build_by_default : false`.
Since 0.46.0 Since 0.46.0
- `protocol` specifies how the test results are parsed and can be one
of `exitcode` (the executable's exit code is used by the test harness
to record the outcome of the test) or `tap` ([Test Anything
Protocol](https://www.testanything.org/)). For more on the Meson test
harness protocol read [Unit Tests](Unit-tests.md). Since 0.50.0
Defined tests can be run in a backend-agnostic way by calling Defined tests can be run in a backend-agnostic way by calling
`meson test` inside the build dir, or by using backend-specific `meson test` inside the build dir, or by using backend-specific
commands, such as `ninja test` or `msbuild RUN_TESTS.vcxproj`. commands, such as `ninja test` or `msbuild RUN_TESTS.vcxproj`.

@ -51,9 +51,15 @@ By default Meson uses as many concurrent processes as there are cores on the tes
$ MESON_TESTTHREADS=5 ninja test $ MESON_TESTTHREADS=5 ninja test
``` ```
## Skipped tests ## Skipped tests and hard errors
Sometimes a test can only determine at runtime that it can not be run. The GNU standard approach in this case is to exit the program with error code 77. Meson will detect this and report these tests as skipped rather than failed. This behavior was added in version 0.37.0. Sometimes a test can only determine at runtime that it can not be run.
For the default `exitcode` testing protocol, the GNU standard approach in this case is to exit the program with error code 77. Meson will detect this and report these tests as skipped rather than failed. This behavior was added in version 0.37.0.
For TAP-based tests, skipped tests should print a single line starting with `1..0 # SKIP`.
In addition, sometimes a test fails set up so that it should fail even if it is marked as an expected failure. The GNU standard approach in this case is to exit the program with error code 99. Again, Meson will detect this and report these tests as `ERROR`, ignoring the setting of `should_fail`. This behavior was added in version 0.50.0.
## Testing tool ## Testing tool

@ -84,7 +84,7 @@ class ExecutableSerialisation:
class TestSerialisation: class TestSerialisation:
def __init__(self, name, project, suite, fname, is_cross_built, exe_wrapper, is_parallel, def __init__(self, name, project, suite, fname, is_cross_built, exe_wrapper, is_parallel,
cmd_args, env, should_fail, timeout, workdir, extra_paths): cmd_args, env, should_fail, timeout, workdir, extra_paths, protocol):
self.name = name self.name = name
self.project_name = project self.project_name = project
self.suite = suite self.suite = suite
@ -100,6 +100,7 @@ class TestSerialisation:
self.timeout = timeout self.timeout = timeout
self.workdir = workdir self.workdir = workdir
self.extra_paths = extra_paths self.extra_paths = extra_paths
self.protocol = protocol
class OptionProxy: class OptionProxy:
def __init__(self, name, value): def __init__(self, name, value):
@ -756,7 +757,7 @@ class Backend:
raise MesonException('Bad object in test command.') raise MesonException('Bad object in test command.')
ts = TestSerialisation(t.get_name(), t.project_name, t.suite, cmd, is_cross, ts = TestSerialisation(t.get_name(), t.project_name, t.suite, cmd, is_cross,
exe_wrapper, t.is_parallel, cmd_args, t.env, exe_wrapper, t.is_parallel, cmd_args, t.env,
t.should_fail, t.timeout, t.workdir, extra_paths) t.should_fail, t.timeout, t.workdir, extra_paths, t.protocol)
arr.append(ts) arr.append(ts)
return arr return arr

@ -850,7 +850,7 @@ class RunTargetHolder(InterpreterObject, ObjectHolder):
class Test(InterpreterObject): class Test(InterpreterObject):
def __init__(self, name, project, suite, exe, depends, is_parallel, def __init__(self, name, project, suite, exe, depends, is_parallel,
cmd_args, env, should_fail, timeout, workdir): cmd_args, env, should_fail, timeout, workdir, protocol):
InterpreterObject.__init__(self) InterpreterObject.__init__(self)
self.name = name self.name = name
self.suite = suite self.suite = suite
@ -863,6 +863,7 @@ class Test(InterpreterObject):
self.should_fail = should_fail self.should_fail = should_fail
self.timeout = timeout self.timeout = timeout
self.workdir = workdir self.workdir = workdir
self.protocol = protocol
def get_exe(self): def get_exe(self):
return self.exe return self.exe
@ -1973,7 +1974,8 @@ permitted_kwargs = {'add_global_arguments': {'language', 'native'},
'library': known_library_kwargs, 'library': known_library_kwargs,
'subdir': {'if_found'}, 'subdir': {'if_found'},
'subproject': {'version', 'default_options', 'required'}, 'subproject': {'version', 'default_options', 'required'},
'test': {'args', 'depends', 'env', 'is_parallel', 'should_fail', 'timeout', 'workdir', 'suite'}, 'test': {'args', 'depends', 'env', 'is_parallel', 'should_fail', 'timeout', 'workdir',
'suite', 'protocol'},
'vcs_tag': {'input', 'output', 'fallback', 'command', 'replace_string'}, 'vcs_tag': {'input', 'output', 'fallback', 'command', 'replace_string'},
} }
@ -3269,6 +3271,9 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
workdir = None workdir = None
if not isinstance(timeout, int): if not isinstance(timeout, int):
raise InterpreterException('Timeout must be an integer.') raise InterpreterException('Timeout must be an integer.')
protocol = kwargs.get('protocol', 'exitcode')
if protocol not in ('exitcode', 'tap'):
raise InterpreterException('Protocol must be "exitcode" or "tap".')
suite = [] suite = []
prj = self.subproject if self.is_subproject() else self.build.project_name prj = self.subproject if self.is_subproject() else self.build.project_name
for s in mesonlib.stringlistify(kwargs.get('suite', '')): for s in mesonlib.stringlistify(kwargs.get('suite', '')):
@ -3280,7 +3285,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
if not isinstance(dep, (build.CustomTarget, build.BuildTarget)): if not isinstance(dep, (build.CustomTarget, build.BuildTarget)):
raise InterpreterException('Depends items must be build targets.') raise InterpreterException('Depends items must be build targets.')
t = Test(args[0], prj, suite, exe.held_object, depends, par, cmd_args, t = Test(args[0], prj, suite, exe.held_object, depends, par, cmd_args,
env, should_fail, timeout, workdir) env, should_fail, timeout, workdir, protocol)
if is_base_test: if is_base_test:
self.build.tests.append(t) self.build.tests.append(t)
mlog.debug('Adding test', mlog.bold(args[0], True)) mlog.debug('Adding test', mlog.bold(args[0], True))

@ -23,6 +23,9 @@ from mesonbuild.dependencies import ExternalProgram
from mesonbuild.mesonlib import substring_is_in_list, MesonException from mesonbuild.mesonlib import substring_is_in_list, MesonException
from mesonbuild import mlog from mesonbuild import mlog
from collections import namedtuple
import io
import re
import tempfile import tempfile
import time, datetime, multiprocessing, json import time, datetime, multiprocessing, json
import concurrent.futures as conc import concurrent.futures as conc
@ -36,6 +39,10 @@ import enum
# mean that the test should be skipped. # mean that the test should be skipped.
GNU_SKIP_RETURNCODE = 77 GNU_SKIP_RETURNCODE = 77
# GNU autotools interprets a return code of 99 from tests it executes to
# mean that the test failed even before testing what it is supposed to test.
GNU_ERROR_RETURNCODE = 99
def is_windows(): def is_windows():
platname = platform.system().lower() platname = platform.system().lower()
return platname == 'windows' or 'mingw' in platname return platname == 'windows' or 'mingw' in platname
@ -146,11 +153,202 @@ class TestResult(enum.Enum):
FAIL = 'FAIL' FAIL = 'FAIL'
EXPECTEDFAIL = 'EXPECTEDFAIL' EXPECTEDFAIL = 'EXPECTEDFAIL'
UNEXPECTEDPASS = 'UNEXPECTEDPASS' UNEXPECTEDPASS = 'UNEXPECTEDPASS'
ERROR = 'ERROR'
class TAPParser(object):
Plan = namedtuple('Plan', ['count', 'late', 'skipped', 'explanation'])
Bailout = namedtuple('Bailout', ['message'])
Test = namedtuple('Test', ['number', 'name', 'result', 'explanation'])
Error = namedtuple('Error', ['message'])
Version = namedtuple('Version', ['version'])
_MAIN = 1
_AFTER_TEST = 2
_YAML = 3
_RE_BAILOUT = r'Bail out!\s*(.*)'
_RE_DIRECTIVE = r'(?:\s*\#\s*([Ss][Kk][Ii][Pp]\S*|[Tt][Oo][Dd][Oo])\b\s*(.*))?'
_RE_PLAN = r'1\.\.([0-9]+)' + _RE_DIRECTIVE
_RE_TEST = r'((?:not )?ok)\s*(?:([0-9]+)\s*)?([^#]*)' + _RE_DIRECTIVE
_RE_VERSION = r'TAP version ([0-9]+)'
_RE_YAML_START = r'(\s+)---.*'
_RE_YAML_END = r'\s+\.\.\.\s*'
def __init__(self, io):
self.io = io
def parse_test(self, ok, num, name, directive, explanation):
name = name.strip()
explanation = explanation.strip() if explanation else None
if directive is not None:
directive = directive.upper()
if directive == 'SKIP':
if ok:
yield self.Test(num, name, TestResult.SKIP, explanation)
return
elif directive == 'TODO':
yield self.Test(num, name, TestResult.UNEXPECTEDPASS if ok else TestResult.EXPECTEDFAIL, explanation)
return
else:
yield self.Error('invalid directive "%s"' % (directive,))
yield self.Test(num, name, TestResult.OK if ok else TestResult.FAIL, explanation)
def parse(self):
found_late_test = False
bailed_out = False
plan = None
lineno = 0
num_tests = 0
yaml_lineno = None
yaml_indent = None
state = self._MAIN
version = 12
while True:
lineno += 1
try:
line = next(self.io).rstrip()
except StopIteration:
break
# YAML blocks are only accepted after a test
if state == self._AFTER_TEST:
if version >= 13:
m = re.match(self._RE_YAML_START, line)
if m:
state = self._YAML
yaml_lineno = lineno
yaml_indent = m.group(1)
continue
state = self._MAIN
elif state == self._YAML:
if re.match(self._RE_YAML_END, line):
state = self._MAIN
continue
if line.startswith(yaml_indent):
continue
yield self.Error('YAML block not terminated (started on line %d)' % (yaml_lineno,))
state = self._MAIN
assert state == self._MAIN
if line.startswith('#'):
continue
m = re.match(self._RE_TEST, line)
if m:
if plan and plan.late and not found_late_test:
yield self.Error('unexpected test after late plan')
found_late_test = True
num_tests += 1
num = num_tests if m.group(2) is None else int(m.group(2))
if num != num_tests:
yield self.Error('out of order test numbers')
yield from self.parse_test(m.group(1) == 'ok', num,
m.group(3), m.group(4), m.group(5))
state = self._AFTER_TEST
continue
m = re.match(self._RE_PLAN, line)
if m:
if plan:
yield self.Error('more than one plan found')
else:
count = int(m.group(1))
skipped = (count == 0)
if m.group(2):
if m.group(2).upper().startswith('SKIP'):
if count > 0:
yield self.Error('invalid SKIP directive for plan')
skipped = True
else:
yield self.Error('invalid directive for plan')
plan = self.Plan(count=count, late=(num_tests > 0),
skipped=skipped, explanation=m.group(3))
yield plan
continue
m = re.match(self._RE_BAILOUT, line)
if m:
yield self.Bailout(m.group(1))
bailed_out = True
continue
m = re.match(self._RE_VERSION, line)
if m:
# The TAP version is only accepted as the first line
if lineno != 1:
yield self.Error('version number must be on the first line')
continue
version = int(m.group(1))
if version < 13:
yield self.Error('version number should be at least 13')
else:
yield self.Version(version=version)
continue
yield self.Error('unexpected input at line %d' % (lineno,))
if state == self._YAML:
yield self.Error('YAML block not terminated (started on line %d)' % (yaml_lineno,))
if not bailed_out and plan and num_tests != plan.count:
if num_tests < plan.count:
yield self.Error('Too few tests run (expected %d, got %d)' % (plan.count, num_tests))
else:
yield self.Error('Too many tests run (expected %d, got %d)' % (plan.count, num_tests))
class TestRun: class TestRun:
def __init__(self, res, returncode, should_fail, duration, stdo, stde, cmd, @staticmethod
env): def make_exitcode(test, returncode, duration, stdo, stde, cmd):
if returncode == GNU_SKIP_RETURNCODE:
res = TestResult.SKIP
elif returncode == GNU_ERROR_RETURNCODE:
res = TestResult.ERROR
elif test.should_fail:
res = TestResult.EXPECTEDFAIL if bool(returncode) else TestResult.UNEXPECTEDPASS
else:
res = TestResult.FAIL if bool(returncode) else TestResult.OK
return TestRun(test, res, returncode, duration, stdo, stde, cmd)
def make_tap(test, returncode, duration, stdo, stde, cmd):
res = None
num_tests = 0
failed = False
num_skipped = 0
for i in TAPParser(io.StringIO(stdo)).parse():
if isinstance(i, TAPParser.Bailout):
res = TestResult.ERROR
elif isinstance(i, TAPParser.Test):
if i.result == TestResult.SKIP:
num_skipped += 1
elif i.result in (TestResult.FAIL, TestResult.UNEXPECTEDPASS):
failed = True
num_tests += 1
elif isinstance(i, TAPParser.Error):
res = TestResult.ERROR
stde += '\nTAP parsing error: ' + i.message
if returncode != 0:
res = TestResult.ERROR
stde += '\n(test program exited with status code %d)' % (returncode,)
if res is None:
# Now determine the overall result of the test based on the outcome of the subcases
if num_skipped == num_tests:
# This includes the case where num_tests is zero
res = TestResult.SKIP
elif test.should_fail:
res = TestResult.EXPECTEDFAIL if failed else TestResult.UNEXPECTEDPASS
else:
res = TestResult.FAIL if failed else TestResult.OK
return TestRun(test, res, returncode, duration, stdo, stde, cmd)
def __init__(self, test, res, returncode, duration, stdo, stde, cmd):
assert isinstance(res, TestResult) assert isinstance(res, TestResult)
self.res = res self.res = res
self.returncode = returncode self.returncode = returncode
@ -158,8 +356,8 @@ class TestRun:
self.stdo = stdo self.stdo = stdo
self.stde = stde self.stde = stde
self.cmd = cmd self.cmd = cmd
self.env = env self.env = test.env
self.should_fail = should_fail self.should_fail = test.should_fail
def get_log(self): def get_log(self):
res = '--- command ---\n' res = '--- command ---\n'
@ -257,9 +455,8 @@ class SingleTestRunner:
cmd = self._get_cmd() cmd = self._get_cmd()
if cmd is None: if cmd is None:
skip_stdout = 'Not run because can not execute cross compiled binaries.' skip_stdout = 'Not run because can not execute cross compiled binaries.'
return TestRun(res=TestResult.SKIP, returncode=GNU_SKIP_RETURNCODE, return TestRun(test=self.test, res=TestResult.SKIP, returncode=GNU_SKIP_RETURNCODE,
should_fail=self.test.should_fail, duration=0.0, duration=0.0, stdo=skip_stdout, stde=None, cmd=None)
stdo=skip_stdout, stde=None, cmd=None, env=self.test.env)
else: else:
wrap = TestHarness.get_wrapper(self.options) wrap = TestHarness.get_wrapper(self.options)
if self.options.gdb: if self.options.gdb:
@ -388,14 +585,12 @@ class SingleTestRunner:
stdo = "" stdo = ""
stde = additional_error stde = additional_error
if timed_out: if timed_out:
res = TestResult.TIMEOUT return TestRun(self.test, TestResult.TIMEOUT, p.returncode, duration, stdo, stde, cmd)
elif p.returncode == GNU_SKIP_RETURNCODE: else:
res = TestResult.SKIP if self.test.protocol == 'exitcode':
elif self.test.should_fail: return TestRun.make_exitcode(self.test, p.returncode, duration, stdo, stde, cmd)
res = TestResult.EXPECTEDFAIL if bool(p.returncode) else TestResult.UNEXPECTEDPASS
else: else:
res = TestResult.FAIL if bool(p.returncode) else TestResult.OK return TestRun.make_tap(self.test, p.returncode, duration, stdo, stde, cmd)
return TestRun(res, p.returncode, self.test.should_fail, duration, stdo, stde, cmd, self.test.env)
class TestHarness: class TestHarness:
@ -471,7 +666,7 @@ class TestHarness:
self.skip_count += 1 self.skip_count += 1
elif result.res is TestResult.OK: elif result.res is TestResult.OK:
self.success_count += 1 self.success_count += 1
elif result.res is TestResult.FAIL: elif result.res is TestResult.FAIL or result.res is TestResult.ERROR:
self.fail_count += 1 self.fail_count += 1
elif result.res is TestResult.EXPECTEDFAIL: elif result.res is TestResult.EXPECTEDFAIL:
self.expectedfail_count += 1 self.expectedfail_count += 1
@ -493,9 +688,11 @@ class TestHarness:
(num, name, padding1, result.res.value, padding2, result.duration, (num, name, padding1, result.res.value, padding2, result.duration,
status) status)
ok_statuses = (TestResult.OK, TestResult.EXPECTEDFAIL) ok_statuses = (TestResult.OK, TestResult.EXPECTEDFAIL)
bad_statuses = (TestResult.FAIL, TestResult.TIMEOUT, TestResult.UNEXPECTEDPASS,
TestResult.ERROR)
if not self.options.quiet or result.res not in ok_statuses: if not self.options.quiet or result.res not in ok_statuses:
if result.res not in ok_statuses and mlog.colorize_console: if result.res not in ok_statuses and mlog.colorize_console:
if result.res in (TestResult.FAIL, TestResult.TIMEOUT, TestResult.UNEXPECTEDPASS): if result.res in bad_statuses:
decorator = mlog.red decorator = mlog.red
elif result.res is TestResult.SKIP: elif result.res is TestResult.SKIP:
decorator = mlog.yellow decorator = mlog.yellow
@ -505,8 +702,7 @@ class TestHarness:
else: else:
print(result_str) print(result_str)
result_str += "\n\n" + result.get_log() result_str += "\n\n" + result.get_log()
if (result.returncode != GNU_SKIP_RETURNCODE) \ if result.res in bad_statuses:
and (result.returncode != 0) != result.should_fail:
if self.options.print_errorlogs: if self.options.print_errorlogs:
self.collected_logs.append(result_str) self.collected_logs.append(result_str)
if self.logfile: if self.logfile:

@ -27,6 +27,7 @@ import unittest
import platform import platform
import pickle import pickle
import functools import functools
import io
from itertools import chain from itertools import chain
from unittest import mock from unittest import mock
from configparser import ConfigParser from configparser import ConfigParser
@ -54,6 +55,8 @@ from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram
from mesonbuild.build import Target from mesonbuild.build import Target
import mesonbuild.modules.pkgconfig import mesonbuild.modules.pkgconfig
from mesonbuild.mtest import TAPParser, TestResult
from run_tests import ( from run_tests import (
Backend, FakeBuild, FakeCompilerOptions, Backend, FakeBuild, FakeCompilerOptions,
ensure_backend_detects_changes, exe_suffix, get_backend_commands, ensure_backend_detects_changes, exe_suffix, get_backend_commands,
@ -5728,6 +5731,272 @@ class CrossFileTests(BasePlatformTests):
'-Ddef_sysconfdir=sysconfbar']) '-Ddef_sysconfdir=sysconfbar'])
class TAPParserTests(unittest.TestCase):
def assert_test(self, events, **kwargs):
if 'explanation' not in kwargs:
kwargs['explanation'] = None
self.assertEqual(next(events), TAPParser.Test(**kwargs))
def assert_plan(self, events, **kwargs):
if 'skipped' not in kwargs:
kwargs['skipped'] = False
if 'explanation' not in kwargs:
kwargs['explanation'] = None
self.assertEqual(next(events), TAPParser.Plan(**kwargs))
def assert_version(self, events, **kwargs):
self.assertEqual(next(events), TAPParser.Version(**kwargs))
def assert_error(self, events):
self.assertEqual(type(next(events)), TAPParser.Error)
def assert_bailout(self, events, **kwargs):
self.assertEqual(next(events), TAPParser.Bailout(**kwargs))
def assert_last(self, events):
with self.assertRaises(StopIteration):
next(events)
def parse_tap(self, s):
parser = TAPParser(io.StringIO(s))
return iter(parser.parse())
def parse_tap_v13(self, s):
events = self.parse_tap('TAP version 13\n' + s)
self.assert_version(events, version=13)
return events
def test_empty(self):
events = self.parse_tap('')
self.assert_last(events)
def test_empty_plan(self):
events = self.parse_tap('1..0')
self.assert_plan(events, count=0, late=False, skipped=True)
self.assert_last(events)
def test_plan_directive(self):
events = self.parse_tap('1..0 # skipped for some reason')
self.assert_plan(events, count=0, late=False, skipped=True,
explanation='for some reason')
self.assert_last(events)
events = self.parse_tap('1..1 # skipped for some reason\nok 1')
self.assert_error(events)
self.assert_plan(events, count=1, late=False, skipped=True,
explanation='for some reason')
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_last(events)
events = self.parse_tap('1..1 # todo not supported here\nok 1')
self.assert_error(events)
self.assert_plan(events, count=1, late=False, skipped=False,
explanation='not supported here')
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_last(events)
def test_one_test_ok(self):
events = self.parse_tap('ok')
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_last(events)
def test_one_test_with_number(self):
events = self.parse_tap('ok 1')
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_last(events)
def test_one_test_with_name(self):
events = self.parse_tap('ok 1 abc')
self.assert_test(events, number=1, name='abc', result=TestResult.OK)
self.assert_last(events)
def test_one_test_not_ok(self):
events = self.parse_tap('not ok')
self.assert_test(events, number=1, name='', result=TestResult.FAIL)
self.assert_last(events)
def test_one_test_todo(self):
events = self.parse_tap('not ok 1 abc # TODO')
self.assert_test(events, number=1, name='abc', result=TestResult.EXPECTEDFAIL)
self.assert_last(events)
events = self.parse_tap('ok 1 abc # TODO')
self.assert_test(events, number=1, name='abc', result=TestResult.UNEXPECTEDPASS)
self.assert_last(events)
def test_one_test_skip(self):
events = self.parse_tap('ok 1 abc # SKIP')
self.assert_test(events, number=1, name='abc', result=TestResult.SKIP)
self.assert_last(events)
def test_one_test_skip_failure(self):
events = self.parse_tap('not ok 1 abc # SKIP')
self.assert_test(events, number=1, name='abc', result=TestResult.FAIL)
self.assert_last(events)
def test_many_early_plan(self):
events = self.parse_tap('1..4\nok 1\nnot ok 2\nok 3\nnot ok 4')
self.assert_plan(events, count=4, late=False)
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
self.assert_test(events, number=3, name='', result=TestResult.OK)
self.assert_test(events, number=4, name='', result=TestResult.FAIL)
self.assert_last(events)
def test_many_late_plan(self):
events = self.parse_tap('ok 1\nnot ok 2\nok 3\nnot ok 4\n1..4')
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
self.assert_test(events, number=3, name='', result=TestResult.OK)
self.assert_test(events, number=4, name='', result=TestResult.FAIL)
self.assert_plan(events, count=4, late=True)
self.assert_last(events)
def test_directive_case(self):
events = self.parse_tap('ok 1 abc # skip')
self.assert_test(events, number=1, name='abc', result=TestResult.SKIP)
self.assert_last(events)
events = self.parse_tap('ok 1 abc # ToDo')
self.assert_test(events, number=1, name='abc', result=TestResult.UNEXPECTEDPASS)
self.assert_last(events)
def test_directive_explanation(self):
events = self.parse_tap('ok 1 abc # skip why')
self.assert_test(events, number=1, name='abc', result=TestResult.SKIP,
explanation='why')
self.assert_last(events)
events = self.parse_tap('ok 1 abc # ToDo Because')
self.assert_test(events, number=1, name='abc', result=TestResult.UNEXPECTEDPASS,
explanation='Because')
self.assert_last(events)
def test_one_test_early_plan(self):
events = self.parse_tap('1..1\nok')
self.assert_plan(events, count=1, late=False)
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_last(events)
def test_one_test_late_plan(self):
events = self.parse_tap('ok\n1..1')
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_plan(events, count=1, late=True)
self.assert_last(events)
def test_out_of_order(self):
events = self.parse_tap('ok 2')
self.assert_error(events)
self.assert_test(events, number=2, name='', result=TestResult.OK)
self.assert_last(events)
def test_middle_plan(self):
events = self.parse_tap('ok 1\n1..2\nok 2')
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_plan(events, count=2, late=True)
self.assert_error(events)
self.assert_test(events, number=2, name='', result=TestResult.OK)
self.assert_last(events)
def test_too_many_plans(self):
events = self.parse_tap('1..1\n1..2\nok 1')
self.assert_plan(events, count=1, late=False)
self.assert_error(events)
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_last(events)
def test_too_many(self):
events = self.parse_tap('ok 1\nnot ok 2\n1..1')
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
self.assert_plan(events, count=1, late=True)
self.assert_error(events)
self.assert_last(events)
events = self.parse_tap('1..1\nok 1\nnot ok 2')
self.assert_plan(events, count=1, late=False)
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
self.assert_error(events)
self.assert_last(events)
def test_too_few(self):
events = self.parse_tap('ok 1\nnot ok 2\n1..3')
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
self.assert_plan(events, count=3, late=True)
self.assert_error(events)
self.assert_last(events)
events = self.parse_tap('1..3\nok 1\nnot ok 2')
self.assert_plan(events, count=3, late=False)
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
self.assert_error(events)
self.assert_last(events)
def test_too_few_bailout(self):
events = self.parse_tap('1..3\nok 1\nnot ok 2\nBail out! no third test')
self.assert_plan(events, count=3, late=False)
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
self.assert_bailout(events, message='no third test')
self.assert_last(events)
def test_diagnostics(self):
events = self.parse_tap('1..1\n# ignored\nok 1')
self.assert_plan(events, count=1, late=False)
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_last(events)
events = self.parse_tap('# ignored\n1..1\nok 1\n# ignored too')
self.assert_plan(events, count=1, late=False)
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_last(events)
events = self.parse_tap('# ignored\nok 1\n1..1\n# ignored too')
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_plan(events, count=1, late=True)
self.assert_last(events)
def test_unexpected(self):
events = self.parse_tap('1..1\ninvalid\nok 1')
self.assert_plan(events, count=1, late=False)
self.assert_error(events)
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_last(events)
def test_version(self):
events = self.parse_tap('TAP version 13\n')
self.assert_version(events, version=13)
self.assert_last(events)
events = self.parse_tap('TAP version 12\n')
self.assert_error(events)
self.assert_last(events)
events = self.parse_tap('1..0\nTAP version 13\n')
self.assert_plan(events, count=0, late=False, skipped=True)
self.assert_error(events)
self.assert_last(events)
def test_yaml(self):
events = self.parse_tap_v13('ok\n ---\n foo: abc\n bar: def\n ...\nok 2')
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_test(events, number=2, name='', result=TestResult.OK)
self.assert_last(events)
events = self.parse_tap_v13('ok\n ---\n foo: abc\n bar: def')
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_error(events)
self.assert_last(events)
events = self.parse_tap_v13('ok 1\n ---\n foo: abc\n bar: def\nnot ok 2')
self.assert_test(events, number=1, name='', result=TestResult.OK)
self.assert_error(events)
self.assert_test(events, number=2, name='', result=TestResult.FAIL)
self.assert_last(events)
def unset_envs(): def unset_envs():
# For unit tests we must fully control all command lines # For unit tests we must fully control all command lines
# so that there are no unexpected changes coming from the # so that there are no unexpected changes coming from the
@ -5741,6 +6010,8 @@ def main():
unset_envs() unset_envs()
cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests', cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests',
'PythonTests', 'NativeFileTests', 'RewriterTests', 'CrossFileTests', 'PythonTests', 'NativeFileTests', 'RewriterTests', 'CrossFileTests',
'TAPParserTests',
'LinuxlikeTests', 'LinuxCrossArmTests', 'LinuxCrossMingwTests', 'LinuxlikeTests', 'LinuxCrossArmTests', 'LinuxCrossMingwTests',
'WindowsTests', 'DarwinTests'] 'WindowsTests', 'DarwinTests']

@ -0,0 +1,10 @@
project('test features', 'c')
tester = executable('tester', 'tester.c')
test('pass', tester, args : ['ok'], protocol: 'tap')
test('fail', tester, args : ['not ok'], should_fail: true, protocol: 'tap')
test('xfail', tester, args : ['not ok # todo'], protocol: 'tap')
test('xpass', tester, args : ['ok # todo'], should_fail: true, protocol: 'tap')
test('skip', tester, args : ['ok # skip'], protocol: 'tap')
test('skip failure', tester, args : ['not ok # skip'], should_fail: true, protocol: 'tap')
test('no tests', tester, args : ['1..0 # skip'], protocol: 'tap')

@ -0,0 +1,10 @@
#include <stdio.h>
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "Incorrect number of arguments, got %i\n", argc);
return 1;
}
puts(argv[1]);
return 0;
}

@ -0,0 +1,3 @@
int main(void) {
return 99;
}

@ -0,0 +1,4 @@
project('trivial', 'c')
# Exit code 99 even overrides should_fail
test('My Test', executable('main', 'main.c'), should_fail: true)

@ -0,0 +1,6 @@
project('test features', 'c')
tester = executable('tester', 'tester.c')
test('nonzero return code', tester, args : [], protocol: 'tap')
test('missing test', tester, args : ['1..1'], protocol: 'tap')
test('incorrect skip', tester, args : ['1..1 # skip\nok 1'], protocol: 'tap')

@ -0,0 +1,10 @@
#include <stdio.h>
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "Incorrect number of arguments, got %i\n", argc);
return 1;
}
puts(argv[1]);
return 0;
}
Loading…
Cancel
Save