From 2235b8e407dbd813a539ad7216ceb2ea5d484154 Mon Sep 17 00:00:00 2001 From: Paul Beckingham Date: Thu, 10 Dec 2015 09:56:43 -0500 Subject: [PATCH] Framework: Added basic test framework --- CMakeLists.txt | 3 + src/CMakeLists.txt | 3 +- test/.gitignore | 2 + test/CMakeLists.txt | 26 +++ test/problems | 132 ++++++++++++ test/run_all | 230 +++++++++++++++++++++ test/test.cpp | 488 ++++++++++++++++++++++++++++++++++++++++++++ test/test.h | 71 +++++++ test/utf8.t.cpp | 98 +++++++++ 9 files changed, 1052 insertions(+), 1 deletion(-) create mode 100644 test/.gitignore create mode 100644 test/CMakeLists.txt create mode 100755 test/problems create mode 100755 test/run_all create mode 100644 test/test.cpp create mode 100644 test/test.h create mode 100644 test/utf8.t.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 93c8bbf6..a07ab440 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b6d5a59..bdde8cad 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 00000000..bec98e37 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,2 @@ +all.log +utf8.t diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..3a2eb0bb --- /dev/null +++ b/test/CMakeLists.txt @@ -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) + diff --git a/test/problems b/test/problems new file mode 100755 index 00000000..677a9761 --- /dev/null +++ b/test/problems @@ -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) diff --git a/test/run_all b/test/run_all new file mode 100755 index 00000000..31ad9d05 --- /dev/null +++ b/test/run_all @@ -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 diff --git a/test/test.cpp b/test/test.cpp new file mode 100644 index 00000000..f860602d --- /dev/null +++ b/test/test.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////////// +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; +} + +/////////////////////////////////////////////////////////////////////////////// diff --git a/test/test.h b/test/test.h new file mode 100644 index 00000000..0b704fea --- /dev/null +++ b/test/test.h @@ -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 + +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 + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/utf8.t.cpp b/test/utf8.t.cpp new file mode 100644 index 00000000..7ad3bd23 --- /dev/null +++ b/test/utf8.t.cpp @@ -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 +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +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 is a test"; + std::string utf8_text_color = "más sábado miércoles"; + std::string utf8_wide_text_color = "改变各种颜色"; + + // 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; +} + +////////////////////////////////////////////////////////////////////////////////