mirror of
https://github.com/GothenburgBitFactory/timewarrior.git
synced 2025-07-07 20:06:39 +02:00
Framework: Added basic test framework
This commit is contained in:
parent
1860a50f69
commit
2235b8e407
9 changed files with 1052 additions and 1 deletions
|
@ -101,6 +101,9 @@ configure_file (
|
|||
${CMAKE_SOURCE_DIR}/cmake.h)
|
||||
|
||||
add_subdirectory (src)
|
||||
if (EXISTS ${CMAKE_SOURCE_DIR}/test)
|
||||
add_subdirectory (test EXCLUDE_FROM_ALL)
|
||||
endif (EXISTS ${CMAKE_SOURCE_DIR}/test)
|
||||
|
||||
set (doc_FILES NEWS ChangeLog README.md INSTALL AUTHORS COPYING LICENSE)
|
||||
foreach (doc_FILE ${doc_FILES})
|
||||
|
|
|
@ -3,7 +3,8 @@ include_directories (${CMAKE_SOURCE_DIR}
|
|||
${CMAKE_SOURCE_DIR}/src
|
||||
${TIMEW_INCLUDE_DIRS})
|
||||
|
||||
set (timew_SRCS utf8.h utf8.cpp)
|
||||
set (timew_SRCS utf8.h utf8.cpp
|
||||
wcwidth6.cpp)
|
||||
|
||||
add_library (timew STATIC ${timew_SRCS})
|
||||
add_executable (timew_executable main.cpp)
|
||||
|
|
2
test/.gitignore
vendored
Normal file
2
test/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
all.log
|
||||
utf8.t
|
26
test/CMakeLists.txt
Normal file
26
test/CMakeLists.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
cmake_minimum_required (VERSION 2.8)
|
||||
if(POLICY CMP0037)
|
||||
cmake_policy(SET CMP0037 OLD)
|
||||
endif()
|
||||
|
||||
include_directories (${CMAKE_SOURCE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${CMAKE_SOURCE_DIR}/test
|
||||
${TIMEW_INCLUDE_DIRS})
|
||||
|
||||
set (test_SRCS utf8.t)
|
||||
|
||||
add_custom_target (test ./run_all --verbose
|
||||
DEPENDS ${test_SRCS} timew_executable
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test)
|
||||
|
||||
add_custom_target (build_tests DEPENDS ${test_SRCS}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/test)
|
||||
|
||||
foreach (src_FILE ${test_SRCS})
|
||||
add_executable (${src_FILE} "${src_FILE}.cpp" test.cpp)
|
||||
target_link_libraries (${src_FILE} timew ${TIMEW_LIBRARIES})
|
||||
endforeach (src_FILE)
|
||||
|
||||
configure_file(run_all run_all COPYONLY)
|
||||
|
132
test/problems
Executable file
132
test/problems
Executable file
|
@ -0,0 +1,132 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import re
|
||||
import argparse
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def color(text, c):
|
||||
"""
|
||||
Add color on the keyword that identifies the state of the test
|
||||
"""
|
||||
if sys.stdout.isatty():
|
||||
clear = "\033[0m"
|
||||
|
||||
colors = {
|
||||
"red": "\033[1m\033[91m",
|
||||
"yellow": "\033[1m\033[93m",
|
||||
"green": "\033[1m\033[92m",
|
||||
}
|
||||
return colors[c] + text + clear
|
||||
else:
|
||||
return text
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Report on test results")
|
||||
parser.add_argument('--summary', action="store_true",
|
||||
help="Display only the totals in each category")
|
||||
parser.add_argument('tapfile', default="all.log", nargs="?",
|
||||
help="File containing TAP output")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def print_category(tests):
|
||||
if not cmd_args.summary:
|
||||
for key in sorted(tests):
|
||||
print("%-32s %4d" % (key, tests[key]))
|
||||
|
||||
|
||||
def pad(i):
|
||||
return " " * i
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cmd_args = parse_args()
|
||||
|
||||
errors = defaultdict(int)
|
||||
skipped = defaultdict(int)
|
||||
expected = defaultdict(int)
|
||||
unexpected = defaultdict(int)
|
||||
passed = defaultdict(int)
|
||||
|
||||
file = re.compile("^# (?:./)?(\S+\.t)(?:\.exe)?$")
|
||||
timestamp = re.compile("^# (\d+(?:\.\d+)?) ==>.*$")
|
||||
start = None
|
||||
stop = None
|
||||
|
||||
with open(cmd_args.tapfile) as fh:
|
||||
for line in fh:
|
||||
if start is None:
|
||||
# First line contains the starting timestamp
|
||||
start = float(timestamp.match(line).group(1))
|
||||
continue
|
||||
|
||||
match = file.match(line)
|
||||
if match:
|
||||
filename = match.group(1)
|
||||
|
||||
if line.startswith("ok "):
|
||||
passed[filename] += 1
|
||||
|
||||
if line.startswith("not "):
|
||||
errors[filename] += 1
|
||||
|
||||
if line.startswith("skip "):
|
||||
skipped[filename] += 1
|
||||
|
||||
if line.startswith("# EXPECTED_FAILURE:"):
|
||||
expected[filename] += 1
|
||||
|
||||
if line.startswith("# UNEXPECTED_SUCCESS:"):
|
||||
unexpected[filename] += 1
|
||||
|
||||
# Last line contains the ending timestamp
|
||||
stop = float(timestamp.match(line).group(1))
|
||||
|
||||
# Remove expected failures from the skipped tests category
|
||||
for filename, value in expected.items():
|
||||
if skipped[filename] == value:
|
||||
del skipped[filename]
|
||||
else:
|
||||
skipped[filename] -= value
|
||||
|
||||
v = "{0:>5d}"
|
||||
passed_str = "Passed:" + pad(24)
|
||||
passed_int = v.format(sum(passed.values()))
|
||||
error_str = "Failed:" + pad(24)
|
||||
error_int = v.format(sum(errors.values()))
|
||||
unexpected_str = "Unexpected successes:" + pad(10)
|
||||
unexpected_int = v.format(sum(unexpected.values()))
|
||||
skipped_str = "Skipped:" + pad(23)
|
||||
skipped_int = v.format(sum(skipped.values()))
|
||||
expected_str = "Expected failures:" + pad(13)
|
||||
expected_int = v.format(sum(expected.values()))
|
||||
runtime_str = "Runtime:" + pad(20)
|
||||
runtime_int = "{0:>8.2f} seconds".format(stop - start)
|
||||
|
||||
if cmd_args.summary:
|
||||
print(color(passed_str, "green"), passed_int)
|
||||
print(color(error_str, "red"), error_int)
|
||||
print(color(unexpected_str, "red"), unexpected_int)
|
||||
print(color(skipped_str, "yellow"), skipped_int)
|
||||
print(color(expected_str, "yellow"), expected_int)
|
||||
print(runtime_str, runtime_int)
|
||||
|
||||
else:
|
||||
print(color(error_str, "red"))
|
||||
print_category(errors)
|
||||
print()
|
||||
print(color(unexpected_str, "red"))
|
||||
print_category(unexpected)
|
||||
print()
|
||||
print(color(skipped_str, "yellow"))
|
||||
print_category(skipped)
|
||||
print()
|
||||
print(color(expected_str, "yellow"))
|
||||
print_category(expected)
|
||||
|
||||
# If we encoutered any failures, return non-zero code
|
||||
sys.exit(1 if error_int or unexpected_int else 0)
|
230
test/run_all
Executable file
230
test/run_all
Executable file
|
@ -0,0 +1,230 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import sys
|
||||
import glob
|
||||
import argparse
|
||||
import logging
|
||||
import time
|
||||
from multiprocessing import cpu_count
|
||||
from threading import Thread
|
||||
from subprocess import call, Popen, PIPE
|
||||
|
||||
try:
|
||||
# python 2
|
||||
from Queue import Queue, Empty
|
||||
except ImportError:
|
||||
# python 3
|
||||
from queue import Queue, Empty
|
||||
|
||||
TIMEOUT = .2
|
||||
|
||||
|
||||
def run_test(testqueue, outqueue, threadname):
|
||||
start = time.time()
|
||||
while True:
|
||||
try:
|
||||
test = testqueue.get(block=True, timeout=TIMEOUT)
|
||||
except Empty:
|
||||
break
|
||||
|
||||
log.info("Running test %s", test)
|
||||
|
||||
try:
|
||||
p = Popen(os.path.abspath(test), stdout=PIPE, stderr=PIPE,
|
||||
env=os.environ)
|
||||
out, err = p.communicate()
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
# Premature end
|
||||
break
|
||||
|
||||
if sys.version_info > (3,):
|
||||
out, err = out.decode('utf-8'), err.decode('utf-8')
|
||||
|
||||
output = ("# {0}\n".format(os.path.basename(test)), out, err)
|
||||
log.debug("Collected output %s", output)
|
||||
outqueue.put(output)
|
||||
|
||||
testqueue.task_done()
|
||||
|
||||
log.warning("Finished %s thread after %s seconds",
|
||||
threadname, round(time.time() - start, 3))
|
||||
|
||||
|
||||
class TestRunner(object):
|
||||
def __init__(self):
|
||||
self.threads = []
|
||||
self.tap = open(cmd_args.tapfile, 'w')
|
||||
|
||||
self._parallelq = Queue()
|
||||
self._serialq = Queue()
|
||||
self._outputq = Queue()
|
||||
|
||||
def _find_tests(self):
|
||||
for test in glob.glob("*.t") + glob.glob("*.t.exe"):
|
||||
if os.access(test, os.X_OK):
|
||||
# Executables only
|
||||
if self._is_parallelizable(test):
|
||||
log.debug("Treating as parallel: %s", test)
|
||||
self._parallelq.put(test)
|
||||
else:
|
||||
log.debug("Treating as serial: %s", test)
|
||||
self._serialq.put(test)
|
||||
else:
|
||||
log.debug("Ignored test %s as it is not executable", test)
|
||||
|
||||
log.info("Parallel tests: %s", self._parallelq.qsize())
|
||||
log.info("Serial tests: %s", self._serialq.qsize())
|
||||
|
||||
def _prepare_threads(self):
|
||||
# Serial thread
|
||||
self.threads.append(
|
||||
Thread(target=run_test, args=(self._serialq, self._outputq, "Serial"))
|
||||
)
|
||||
# Parallel threads
|
||||
self.threads.extend([
|
||||
Thread(target=run_test, args=(self._parallelq, self._outputq, "Parallel"))
|
||||
for i in range(cpu_count())
|
||||
])
|
||||
log.info("Spawned %s threads to run tests", len(self.threads))
|
||||
|
||||
def _start_threads(self):
|
||||
for thread in self.threads:
|
||||
# Threads die when main thread dies
|
||||
log.debug("Starting thread %s", thread)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def _print_timestamp_to_tap(self):
|
||||
now = time.time()
|
||||
timestamp = "# {0} ==> {1}\n".format(
|
||||
now,
|
||||
time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(now)),
|
||||
)
|
||||
|
||||
log.debug("Adding timestamp %s to TAP file", timestamp)
|
||||
self.tap.write(timestamp)
|
||||
|
||||
def _is_parallelizable(self, test):
|
||||
if cmd_args.serial:
|
||||
return False
|
||||
|
||||
# This is a pretty weird way to do it, and not realiable.
|
||||
# We are dealing with some binary tests though.
|
||||
with open(test, 'rb') as fh:
|
||||
header = fh.read(100).split(b"\n")
|
||||
if len(header) >= 2 and \
|
||||
((b"/usr/bin/env python" in header[0]) or \
|
||||
(header[1][-14:] == b"bash_tap_tw.sh")):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _get_remaining_tests(self):
|
||||
return self._parallelq.qsize() + self._serialq.qsize()
|
||||
|
||||
def is_running(self):
|
||||
for thread in self.threads:
|
||||
if thread.is_alive():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def start(self):
|
||||
self._find_tests()
|
||||
self._prepare_threads()
|
||||
|
||||
self._print_timestamp_to_tap()
|
||||
|
||||
finished = 0
|
||||
total = self._get_remaining_tests()
|
||||
|
||||
self._start_threads()
|
||||
|
||||
while self.is_running() or not self._outputq.empty():
|
||||
try:
|
||||
outputs = self._outputq.get(block=True, timeout=TIMEOUT)
|
||||
except Empty:
|
||||
continue
|
||||
|
||||
log.debug("Outputting to TAP: %s", outputs)
|
||||
|
||||
for output in outputs:
|
||||
self.tap.write(output)
|
||||
|
||||
if cmd_args.verbose:
|
||||
sys.stdout.write(output)
|
||||
|
||||
self._outputq.task_done()
|
||||
finished += 1
|
||||
|
||||
log.warning("Finished %s out of %s tests", finished, total)
|
||||
|
||||
self._print_timestamp_to_tap()
|
||||
|
||||
if not self._parallelq.empty() or not self._serialq.empty():
|
||||
raise RuntimeError(
|
||||
"Something went wrong, not all tests were ran. {0} "
|
||||
"remaining.".format(self._get_remaining_tests()))
|
||||
|
||||
def show_report(self):
|
||||
self.tap.flush()
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
|
||||
log.debug("Calling 'problems --summary' for report")
|
||||
return call([os.path.abspath("problems"), "--summary", cmd_args.tapfile])
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Run Timewarrior tests")
|
||||
parser.add_argument('--verbose', '-v', action="store_true",
|
||||
help="Also send TAP output to stdout")
|
||||
parser.add_argument('--logging', '-l', action="count",
|
||||
default=0,
|
||||
help="Logging level. -lll is the highest level")
|
||||
parser.add_argument('--serial', action="store_true",
|
||||
help="Do not run tests in parallel")
|
||||
parser.add_argument('--tapfile', default="all.log",
|
||||
help="File to use for TAP output")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
runner = TestRunner()
|
||||
runner.start()
|
||||
|
||||
# Propagate the return code
|
||||
return runner.show_report()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cmd_args = parse_args()
|
||||
|
||||
if cmd_args.logging == 1:
|
||||
level = logging.WARN
|
||||
elif cmd_args.logging == 2:
|
||||
level = logging.INFO
|
||||
elif cmd_args.logging >= 3:
|
||||
level = logging.DEBUG
|
||||
else:
|
||||
level = logging.ERROR
|
||||
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||
level=level,
|
||||
)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
log.debug("Parsed commandline arguments: %s", cmd_args)
|
||||
|
||||
try:
|
||||
sys.exit(main())
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
sys.exit(1)
|
||||
|
||||
# vim: ai sts=4 et sw=4
|
488
test/test.cpp
Normal file
488
test/test.cpp
Normal file
|
@ -0,0 +1,488 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright 2006 - 2015, Paul Beckingham, Federico Hernandez.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// http://www.opensource.org/licenses/mit-license.php
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <test.h>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
UnitTest::UnitTest ()
|
||||
: _planned (0)
|
||||
, _counter (0)
|
||||
, _passed (0)
|
||||
, _failed (0)
|
||||
, _skipped (0)
|
||||
{
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
UnitTest::UnitTest (int planned)
|
||||
: _planned (planned)
|
||||
, _counter (0)
|
||||
, _passed (0)
|
||||
, _failed (0)
|
||||
, _skipped (0)
|
||||
{
|
||||
std::cout << "1.." << _planned << "\n";
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
UnitTest::~UnitTest ()
|
||||
{
|
||||
float percentPassed = 0.0;
|
||||
if (_planned > 0)
|
||||
percentPassed = (100.0 * _passed) / std::max (_planned, _passed + _failed + _skipped);
|
||||
|
||||
if (_counter < _planned)
|
||||
{
|
||||
std::cout << "# Only "
|
||||
<< _counter
|
||||
<< " tests, out of a planned "
|
||||
<< _planned
|
||||
<< " were run.\n";
|
||||
_skipped += _planned - _counter;
|
||||
}
|
||||
|
||||
else if (_counter > _planned)
|
||||
std::cout << "# "
|
||||
<< _counter
|
||||
<< " tests were run, but only "
|
||||
<< _planned
|
||||
<< " were planned.\n";
|
||||
|
||||
std::cout << "# "
|
||||
<< _passed
|
||||
<< " passed, "
|
||||
<< _failed
|
||||
<< " failed, "
|
||||
<< _skipped
|
||||
<< " skipped. "
|
||||
<< std::setprecision (3) << percentPassed
|
||||
<< "% passed.\n";
|
||||
exit (_failed > 0);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::plan (int planned)
|
||||
{
|
||||
_planned = planned;
|
||||
_counter = 0;
|
||||
_passed = 0;
|
||||
_failed = 0;
|
||||
_skipped = 0;
|
||||
|
||||
std::cout << "1.." << _planned << "\n";
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::planMore (int extra)
|
||||
{
|
||||
_planned += extra;
|
||||
std::cout << "1.." << _planned << "\n";
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::ok (bool expression, const std::string& name)
|
||||
{
|
||||
++_counter;
|
||||
|
||||
if (expression)
|
||||
{
|
||||
++_passed;
|
||||
std::cout << green ("ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
++_failed;
|
||||
std::cout << red ("not ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::notok (bool expression, const std::string& name)
|
||||
{
|
||||
++_counter;
|
||||
|
||||
if (!expression)
|
||||
{
|
||||
++_passed;
|
||||
std::cout << green ("ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
++_failed;
|
||||
std::cout << red ("not ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::is (bool actual, bool expected, const std::string& name)
|
||||
{
|
||||
++_counter;
|
||||
if (actual == expected)
|
||||
{
|
||||
++_passed;
|
||||
std::cout << green ("ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
++_failed;
|
||||
std::cout << red ("not ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n# expected: "
|
||||
<< expected
|
||||
<< "\n# got: "
|
||||
<< actual
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::is (size_t actual, size_t expected, const std::string& name)
|
||||
{
|
||||
++_counter;
|
||||
if (actual == expected)
|
||||
{
|
||||
++_passed;
|
||||
std::cout << green ("ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
++_failed;
|
||||
std::cout << red ("not ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n# expected: "
|
||||
<< expected
|
||||
<< "\n# got: "
|
||||
<< actual
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::is (int actual, int expected, const std::string& name)
|
||||
{
|
||||
++_counter;
|
||||
if (actual == expected)
|
||||
{
|
||||
++_passed;
|
||||
std::cout << green ("ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
++_failed;
|
||||
std::cout << red ("not ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n# expected: "
|
||||
<< expected
|
||||
<< "\n# got: "
|
||||
<< actual
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::is (double actual, double expected, const std::string& name)
|
||||
{
|
||||
++_counter;
|
||||
if (actual == expected)
|
||||
{
|
||||
++_passed;
|
||||
std::cout << green ("ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
++_failed;
|
||||
std::cout << red ("not ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n# expected: "
|
||||
<< expected
|
||||
<< "\n# got: "
|
||||
<< actual
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::is (double actual, double expected, double tolerance, const std::string& name)
|
||||
{
|
||||
++_counter;
|
||||
if (fabs (actual - expected) <= tolerance)
|
||||
{
|
||||
++_passed;
|
||||
std::cout << green ("ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
++_failed;
|
||||
std::cout << red ("not ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n# expected: "
|
||||
<< expected
|
||||
<< "\n# got: "
|
||||
<< actual
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::is (unsigned char actual, unsigned char expected, const std::string& name)
|
||||
{
|
||||
++_counter;
|
||||
if (actual == expected)
|
||||
{
|
||||
++_passed;
|
||||
std::cout << green ("ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
++_failed;
|
||||
std::cout << red ("not ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n# expected: "
|
||||
<< expected
|
||||
<< "\n# got: "
|
||||
<< actual
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::is (
|
||||
const std::string& actual,
|
||||
const std::string& expected,
|
||||
const std::string& name)
|
||||
{
|
||||
++_counter;
|
||||
if (actual == expected)
|
||||
{
|
||||
++_passed;
|
||||
std::cout << green ("ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
++_failed;
|
||||
std::cout << red ("not ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n# expected: '"
|
||||
<< expected
|
||||
<< "'"
|
||||
<< "\n# got: '"
|
||||
<< actual
|
||||
<< "'\n";
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::is (
|
||||
const char* actual,
|
||||
const char* expected,
|
||||
const std::string& name)
|
||||
{
|
||||
++_counter;
|
||||
if (! strcmp (actual, expected))
|
||||
{
|
||||
++_passed;
|
||||
std::cout << green ("ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
++_failed;
|
||||
std::cout << red ("not ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< name
|
||||
<< "\n# expected: '"
|
||||
<< expected
|
||||
<< "'"
|
||||
<< "\n# got: '"
|
||||
<< actual
|
||||
<< "'\n";
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::diag (const std::string& text)
|
||||
{
|
||||
auto start = text.find_first_not_of (" \t\n\r\f");
|
||||
auto end = text.find_last_not_of (" \t\n\r\f");
|
||||
std::cout << "# " << text.substr (start, end - start + 1) << "\n";
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::pass (const std::string& text)
|
||||
{
|
||||
++_counter;
|
||||
++_passed;
|
||||
std::cout << green ("ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< text
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::fail (const std::string& text)
|
||||
{
|
||||
++_counter;
|
||||
++_failed;
|
||||
std::cout << red ("not ok")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< text
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void UnitTest::skip (const std::string& text)
|
||||
{
|
||||
++_counter;
|
||||
++_skipped;
|
||||
std::cout << yellow ("skip")
|
||||
<< " "
|
||||
<< _counter
|
||||
<< " - "
|
||||
<< text
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
std::string UnitTest::red (const std::string& input)
|
||||
{
|
||||
if (isatty (fileno (stdout)))
|
||||
return std::string ("\033[31m" + input + "\033[0m");
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
std::string UnitTest::green (const std::string& input)
|
||||
{
|
||||
if (isatty (fileno (stdout)))
|
||||
return std::string ("\033[32m" + input + "\033[0m");
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
std::string UnitTest::yellow (const std::string& input)
|
||||
{
|
||||
if (isatty (fileno (stdout)))
|
||||
return std::string ("\033[33m" + input + "\033[0m");
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
71
test/test.h
Normal file
71
test/test.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright 2006 - 2015, Paul Beckingham, Federico Hernandez.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// http://www.opensource.org/licenses/mit-license.php
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDED_UNITTEST
|
||||
#define INCLUDED_UNITTEST
|
||||
|
||||
#include <string>
|
||||
|
||||
class UnitTest
|
||||
{
|
||||
public:
|
||||
UnitTest ();
|
||||
UnitTest (int);
|
||||
~UnitTest ();
|
||||
|
||||
void plan (int);
|
||||
void planMore (int);
|
||||
void ok (bool, const std::string&);
|
||||
void notok (bool, const std::string&);
|
||||
void is (bool, bool, const std::string&);
|
||||
void is (size_t, size_t, const std::string&);
|
||||
void is (int, int, const std::string&);
|
||||
void is (double, double, const std::string&);
|
||||
void is (double, double, double, const std::string&);
|
||||
void is (unsigned char, unsigned char, const std::string&);
|
||||
void is (const std::string&, const std::string&, const std::string&);
|
||||
void is (const char*, const char*, const std::string&);
|
||||
void diag (const std::string&);
|
||||
void pass (const std::string&);
|
||||
void fail (const std::string&);
|
||||
void skip (const std::string&);
|
||||
|
||||
private:
|
||||
std::string red (const std::string&);
|
||||
std::string green (const std::string&);
|
||||
std::string yellow (const std::string&);
|
||||
|
||||
private:
|
||||
int _planned;
|
||||
int _counter;
|
||||
int _passed;
|
||||
int _failed;
|
||||
int _skipped;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
98
test/utf8.t.cpp
Normal file
98
test/utf8.t.cpp
Normal file
|
@ -0,0 +1,98 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright 2006 - 2015, Paul Beckingham, Federico Hernandez.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// http://www.opensource.org/licenses/mit-license.php
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cmake.h>
|
||||
#include <utf8.h>
|
||||
#include <test.h>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
int main (int, char**)
|
||||
{
|
||||
UnitTest t (33);
|
||||
|
||||
std::string ascii_text = "This is a test";
|
||||
std::string utf8_text = "más sábado miércoles";
|
||||
std::string utf8_wide_text = "改变各种颜色";
|
||||
|
||||
std::string ascii_text_color = "This [1mis[0m a test";
|
||||
std::string utf8_text_color = "más [1msábado[0m miércoles";
|
||||
std::string utf8_wide_text_color = "改[1m变各种[0m颜色";
|
||||
|
||||
// unsigned int utf8_codepoint (const std::string&);
|
||||
t.is ((int) utf8_codepoint ("\\u0020"), 32, "\\u0020 --> ' '");
|
||||
t.is ((int) utf8_codepoint ("U+0020"), 32, "U+0020 --> ' '");
|
||||
|
||||
// TODO unsigned int utf8_next_char (const std::string&, std::string::size_type&);
|
||||
// TODO std::string utf8_character (unsigned int);
|
||||
// TODO int utf8_sequence (unsigned int);
|
||||
|
||||
// unsigned int utf8_length (const std::string&);
|
||||
t.is ((int) utf8_length (ascii_text), 14, "ASCII utf8_length");
|
||||
t.is ((int) utf8_length (utf8_text), 20, "UTF8 utf8_length");
|
||||
t.is ((int) utf8_length (utf8_wide_text), 6, "UTF8 wide utf8_length");
|
||||
|
||||
// unsigned int utf8_width (const std::string&);
|
||||
t.is ((int) utf8_width (ascii_text), 14, "ASCII utf8_width");
|
||||
t.is ((int) utf8_width (utf8_text), 20, "UTF8 utf8_width");
|
||||
t.is ((int) utf8_width (utf8_wide_text), 12, "UTF8 wide utf8_width");
|
||||
|
||||
// unsigned int utf8_text_length (const std::string&);
|
||||
t.is ((int) utf8_text_length (ascii_text_color), 14, "ASCII utf8_text_length");
|
||||
t.is ((int) utf8_text_length (utf8_text_color), 20, "UTF8 utf8_text_length");
|
||||
t.is ((int) utf8_text_length (utf8_wide_text_color), 6, "UTF8 wide utf8_text_length");
|
||||
|
||||
// unsigned int utf8_text_width (const std::string&);
|
||||
t.is ((int) utf8_text_width (ascii_text_color), 14, "ASCII utf8_text_width");
|
||||
t.is ((int) utf8_text_width (utf8_text_color), 20, "UTF8 utf8_text_width");
|
||||
t.is ((int) utf8_text_width (utf8_wide_text_color), 12, "UTF8 wide utf8_text_width");
|
||||
|
||||
// const std::string utf8_substr (const std::string&, unsigned int, unsigned int length = 0);
|
||||
t.is (utf8_substr (ascii_text, 0, 2), "Th", "ASCII utf8_substr");
|
||||
t.is (utf8_substr (utf8_text, 0, 2), "má", "UTF8 utf8_substr");
|
||||
t.is (utf8_substr (utf8_wide_text, 0, 2), "改变", "UTF8 wide utf8_substr");
|
||||
|
||||
// int mk_wcwidth (wchar_t);
|
||||
t.is (mk_wcwidth ('a'), 1, "mk_wcwidth U+0061 --> 1");
|
||||
t.is (mk_wcwidth (0x5149), 2, "mk_wcwidth U+5149 --> 2");
|
||||
t.is (mk_wcwidth (0x9a8c), 2, "mk_wcwidth U+9a8c --> 2");
|
||||
t.is (mk_wcwidth (0x4e70), 2, "mk_wcwidth U+4e70 --> 2");
|
||||
t.is (mk_wcwidth (0x94b1), 2, "mk_wcwidth U+94b1 --> 2");
|
||||
t.is (mk_wcwidth (0x5305), 2, "mk_wcwidth U+5305 --> 2");
|
||||
t.is (mk_wcwidth (0x91cd), 2, "mk_wcwidth U+91cd --> 2");
|
||||
t.is (mk_wcwidth (0x65b0), 2, "mk_wcwidth U+65b0 --> 2");
|
||||
t.is (mk_wcwidth (0x8bbe), 2, "mk_wcwidth U+8bbe --> 2");
|
||||
t.is (mk_wcwidth (0x8ba1), 2, "mk_wcwidth U+8ba1 --> 2");
|
||||
t.is (mk_wcwidth (0x5411), 2, "mk_wcwidth U+5411 --> 2");
|
||||
t.is (mk_wcwidth (0x4e0a), 2, "mk_wcwidth U+4e0a --> 2");
|
||||
t.is (mk_wcwidth (0x4e0b), 2, "mk_wcwidth U+4e0b --> 2");
|
||||
t.is (mk_wcwidth (0x7bad), 2, "mk_wcwidth U+7bad --> 2");
|
||||
t.is (mk_wcwidth (0x5934), 2, "mk_wcwidth U+5934 --> 2");
|
||||
t.is (mk_wcwidth (0xff0c), 2, "mk_wcwidth U+ff0c --> 2"); // comma
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
Loading…
Add table
Add a link
Reference in a new issue