Framework: Added basic test framework

This commit is contained in:
Paul Beckingham 2015-12-10 09:56:43 -05:00
parent 1860a50f69
commit 2235b8e407
9 changed files with 1052 additions and 1 deletions

View file

@ -101,6 +101,9 @@ configure_file (
${CMAKE_SOURCE_DIR}/cmake.h) ${CMAKE_SOURCE_DIR}/cmake.h)
add_subdirectory (src) 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) set (doc_FILES NEWS ChangeLog README.md INSTALL AUTHORS COPYING LICENSE)
foreach (doc_FILE ${doc_FILES}) foreach (doc_FILE ${doc_FILES})

View file

@ -3,7 +3,8 @@ include_directories (${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src
${TIMEW_INCLUDE_DIRS}) ${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_library (timew STATIC ${timew_SRCS})
add_executable (timew_executable main.cpp) add_executable (timew_executable main.cpp)

2
test/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
all.log
utf8.t

26
test/CMakeLists.txt Normal file
View 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
View 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
View 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
View 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
View 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
View 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 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), "", "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;
}
////////////////////////////////////////////////////////////////////////////////