summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:26:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:26:42 +0000
commitdaf0485ec77463dbaeba3b1b0ffeefc8a89f5399 (patch)
tree1542cc4a202acd6b3e3b7c1729ede0e94750b691 /tests
parentInitial commit. (diff)
downloadsexpp-upstream.tar.xz
sexpp-upstream.zip
Adding upstream version 0.8.7.upstream/0.8.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests')
-rw-r--r--tests/include/sexp-tests.h42
-rwxr-xr-xtests/scripts/tests.sh191
-rw-r--r--tests/src/baseline-tests.cpp115
-rw-r--r--tests/src/compare-files.cpp126
-rw-r--r--tests/src/exception-tests.cpp331
-rw-r--r--tests/src/g10-compat-tests.cpp51
-rw-r--r--tests/src/g23-compat-tests.cpp139
-rw-r--r--tests/src/g23-exception-tests.cpp108
-rw-r--r--tests/src/primitives-tests.cpp394
9 files changed, 1497 insertions, 0 deletions
diff --git a/tests/include/sexp-tests.h b/tests/include/sexp-tests.h
new file mode 100644
index 0000000..497fc91
--- /dev/null
+++ b/tests/include/sexp-tests.h
@@ -0,0 +1,42 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ */
+
+#pragma once
+
+#include <cstdio>
+#include <iostream>
+#include <fstream>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "sexpp/sexp.h"
+#include "sexp-samples-folder.h"
+
+bool compare_binary_files(const std::string &filename1, const std::string &filename2);
+bool compare_text_files(const std::string &filename1, const std::string &filename2);
+
+bool compare_binary_files(const std::string &filename1, std::istream &file2);
+bool compare_text_files(const std::string &filename1, std::istream &file2);
+
+std::istream &safe_get_line(std::istream &is, std::string &t);
diff --git a/tests/scripts/tests.sh b/tests/scripts/tests.sh
new file mode 100755
index 0000000..4c37c78
--- /dev/null
+++ b/tests/scripts/tests.sh
@@ -0,0 +1,191 @@
+#! /bin/bash
+#
+# Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+#
+# 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.
+#
+
+# More safety, by turning some bugs into errors.
+# Without `errexit` you don’t need ! and can replace
+# PIPESTATUS with a simple $?
+set -o errexit -o pipefail -o noclobber -o nounset
+
+assert_installed() {
+ assertTrue "$1 was not installed" "[ -f $1 ]"
+}
+
+assert_installed_var() {
+ assertTrue "{$1,$2}/$3 was not installed" "[ -f $1/$3 ] || [ -f $2/$3 ]"
+}
+
+assert_installed_var2() {
+ assertTrue "$1/{$2,$3} was not installed" "[ -f $1/$2 ] || [ -f $1/$3 ]"
+}
+
+# ......................................................................
+# Check that sexp is installed as expected
+test_install_script() {
+ echo "==> Install script test"
+
+ if [[ "${SHARED_LIB:-}" == "on" ]]; then
+ version=$(cat "$DIR_ROOT"/version.txt)
+ version_major="${version:0:1}"
+ case "$OSTYPE" in
+ darwin*)
+ assert_installed "$DIR_INS_B/sexpp"
+ assert_installed "$DIR_INS_L/libsexpp.dylib"
+ assert_installed "$DIR_INS_L/libsexpp.$version_major.dylib"
+ assert_installed "$DIR_INS_L/libsexpp.$version.dylib"
+ ;;
+ windows )
+ assert_installed "$DIR_INS_B/sexpp.exe"
+ assert_installed "$DIR_INS_B/sexpp.dll"
+ assert_installed "$DIR_INS_L/sexpp.lib"
+ ;;
+ msys)
+
+ assert_installed "$DIR_INS_B/sexpp.exe"
+ assert_installed "$DIR_INS_B/libsexpp.dll"
+ assert_installed "$DIR_INS_L/libsexpp.dll.a"
+ ;;
+ *)
+ assert_installed "$DIR_INS_B/sexpp"
+ assert_installed_var "$DIR_INS_L" "$DIR_INS_L64" "libsexpp.so"
+ assert_installed_var "$DIR_INS_L" "$DIR_INS_L64" "libsexpp.so.$version_major"
+ assert_installed_var "$DIR_INS_L" "$DIR_INS_L64" "libsexpp.so.$version"
+ ;;
+ esac
+ else
+ case "$OSTYPE" in
+ windows)
+ assert_installed "$DIR_INS_B/sexpp.exe"
+ assert_installed "$DIR_INS_L/sexpp.lib"
+ ;;
+ msys)
+ assert_installed "$DIR_INS_B/sexpp.exe"
+ assert_installed "$DIR_INS_L/libsexpp.a"
+ ;;
+ *)
+ assert_installed "$DIR_INS_B/sexpp"
+ assert_installed_var "$DIR_INS_L" "$DIR_INS_L64" "libsexpp.a"
+ ;;
+ esac
+ fi
+
+ assert_installed_var "$DIR_INS_P" "$DIR_INS_P64" "sexpp.pc"
+ assert_installed_var2 "$DIR_INS_M/man1" "sexpp.1" "sexpp.1.gz"
+
+ assert_installed "$DIR_INS_I/sexp.h"
+ assert_installed "$DIR_INS_I/sexp-error.h"
+}
+
+# ......................................................................
+# Check sexp client application
+# THese are the examples from README.adoc
+test_sexp_cli() {
+ echo "==> SEXP client application test"
+
+# On Windows there will be CRLF vs LS mismatch
+# We would rather skip these tests
+ if [[ "$OSTYPE" == "windows" || "$OSTYPE" == "msys" ]]; then
+ startSkipping
+ fi
+
+ app="$DIR_INS_B/sexpp"
+# shellcheck disable=SC2251
+! IFS= read -r -d '' expected << EOM
+Input:
+
+Writing base64 (of canonical) output to certificate.dat
+EOM
+ export LD_LIBRARY_PATH="$DIR_INS_L":"$DIR_INS_L64"
+ rm -f input1.dat
+ echo "(aa bb (cc dd))" > input1.dat
+ output=$("$app" -o certificate.dat -p -b < input1.dat)
+# $expected possibly includes extra EOL at the end -- it depends on OS
+ assertContains "$expected" "$output"
+ output=$(cat certificate.dat)
+ assertEquals "{KDI6YWEyOmJiKDI6Y2MyOmRkKSk=}" "$output"
+
+ output=$("$app" -i certificate.dat -x)
+ assertEquals "(2:aa2:bb(2:cc2:dd))" "$output"
+
+# shellcheck disable=SC2251
+! IFS= read -r -d '' expected << EOM
+Reading input from certificate.dat
+
+Canonical output:
+(2:aa2:bb(2:cc2:dd))
+Base64 (of canonical) output:
+{KDI6YWEyOmJiKDI6Y2MyOmRkKSk=}
+Advanced transport output:
+(aa bb (cc dd))
+EOM
+
+ output=$("$app" -i certificate.dat -a -b -c -p -w 0)
+ assertContains "$expected" "$output"
+
+# shellcheck disable=SC2251
+! IFS= read -r -d '' expected << EOM
+Input:
+
+Canonical output:
+(3:abc3:def(3:ghi3:jkl))
+Base64 (of canonical) output:
+{KDM6YWJjMzpkZWYoMzpnaGkzOmprbCkp}
+Advanced transport output:
+(abc def (ghi jkl))
+
+Input:
+EOM
+ rm -f input2.dat
+ echo "(abc def (ghi jkl))" > input2.dat
+ output=$("$app" < input2.dat)
+ assertContains "$expected" "$output"
+
+ if [[ "$OSTYPE" == "windows" || "$OSTYPE" == "msys" ]]; then
+ endSkipping
+ fi
+
+}
+
+# ......................................................................
+# main
+
+DIR00=$( dirname "$0" )
+DIR0=$( cd "$DIR00" && pwd )
+DIR1="${DIR_ROOT:=$DIR0/../..}"
+DIR_ROOT=$( cd "$DIR1" && pwd )
+
+if [[ -z "${DIR_INSTALL:-}" ]]; then
+ DIR_INSTALL="$DIR_ROOT/install"
+fi
+
+DIR_INS_B="$DIR_INSTALL/bin"
+DIR_INS_L="$DIR_INSTALL/lib"
+DIR_INS_L64="$DIR_INSTALL/lib64"
+DIR_INS_M="$DIR_INSTALL/share/man"
+DIR_INS_P="$DIR_INS_L/pkgconfig"
+DIR_INS_P64="$DIR_INS_L64/pkgconfig"
+DIR_INS_I="$DIR_INSTALL/include/sexpp"
+
+DIR_TESTS=$( cd "$DIR0/.." && pwd)
+
+echo "Running sexp additional tests"
+# shellcheck source=/dev/null
+. "$DIR_TESTS"/shunit2/shunit2
diff --git a/tests/src/baseline-tests.cpp b/tests/src/baseline-tests.cpp
new file mode 100644
index 0000000..bbd59a4
--- /dev/null
+++ b/tests/src/baseline-tests.cpp
@@ -0,0 +1,115 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ */
+
+#include "sexp-tests.h"
+
+using namespace sexp;
+using ::testing::UnitTest;
+
+namespace {
+class BaselineTests : public testing::Test {
+ protected:
+ static const uint32_t base_sample_advanced = 0;
+ static const uint32_t base_sample_base64 = 1;
+ static const uint32_t base_sample_canonical = 2;
+ static const uint32_t n_base_samples = base_sample_canonical + 1;
+
+ static std::string base_samples[n_base_samples];
+
+ BaselineTests()
+ {
+ base_samples[base_sample_advanced] = sexp_samples_folder + "/baseline/sexp-sample-a";
+ base_samples[base_sample_base64] = sexp_samples_folder + "/baseline/sexp-sample-b";
+ base_samples[base_sample_canonical] = sexp_samples_folder + "/baseline/sexp-sample-c";
+ };
+};
+
+const uint32_t BaselineTests::n_base_samples;
+const uint32_t BaselineTests::base_sample_advanced;
+const uint32_t BaselineTests::base_sample_base64;
+const uint32_t BaselineTests::base_sample_canonical;
+std::string BaselineTests::base_samples[n_base_samples];
+
+TEST_F(BaselineTests, Scan2Canonical)
+{
+ for (uint32_t i = 0; i < n_base_samples; i++) {
+ std::ifstream ifs(base_samples[i], std::ifstream::binary);
+ bool r = ifs.fail();
+ EXPECT_FALSE(r);
+
+ if (!ifs.fail()) {
+ sexp_input_stream_t is(&ifs);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.print_canonical(obj);
+
+ std::istringstream iss(oss.str(), std::ios_base::binary);
+ EXPECT_TRUE(compare_binary_files(base_samples[base_sample_canonical], iss));
+ }
+ }
+}
+
+TEST_F(BaselineTests, Scan2Base64)
+{
+ for (uint32_t i = 0; i < n_base_samples; i++) {
+ std::ifstream ifs(base_samples[i], std::ifstream::binary);
+ EXPECT_FALSE(ifs.fail());
+
+ if (!ifs.fail()) {
+ sexp_input_stream_t is(&ifs);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+
+ os.set_max_column(0)->print_base64(obj);
+ oss << std::endl;
+
+ std::istringstream iss(oss.str(), std::ios_base::binary);
+ EXPECT_TRUE(compare_text_files(base_samples[base_sample_base64], iss));
+ }
+ }
+}
+
+TEST_F(BaselineTests, Scan2Advanced)
+{
+ for (uint32_t i = 0; i < n_base_samples; i++) {
+ std::ifstream ifs(base_samples[i], std::ifstream::binary);
+ EXPECT_FALSE(ifs.fail());
+
+ if (!ifs.fail()) {
+ sexp_input_stream_t is(&ifs);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.print_advanced(obj);
+
+ std::istringstream iss(oss.str(), std::ios_base::binary);
+ EXPECT_TRUE(compare_text_files(base_samples[base_sample_advanced], iss));
+ }
+ }
+}
+} // namespace
diff --git a/tests/src/compare-files.cpp b/tests/src/compare-files.cpp
new file mode 100644
index 0000000..49caec5
--- /dev/null
+++ b/tests/src/compare-files.cpp
@@ -0,0 +1,126 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ */
+
+#include <algorithm>
+#include <fstream>
+
+#include "sexp-tests.h"
+
+bool compare_binary_files(const std::string &filename1, const std::string &filename2)
+{
+ bool res = false;
+ std::ifstream file1(filename1, std::ifstream::ate | std::ifstream::binary);
+ std::ifstream file2(filename2, std::ifstream::ate | std::ifstream::binary);
+
+ if (file1.tellg() == file2.tellg()) { // otherwise different file size
+ file1.seekg(0);
+ file2.seekg(0);
+
+ std::istreambuf_iterator<char> begin1(file1);
+ std::istreambuf_iterator<char> begin2(file2);
+
+ res = std::equal(begin1, std::istreambuf_iterator<char>(), begin2);
+ }
+
+ return res;
+}
+
+bool compare_binary_files(const std::string &filename1, std::istream &file2)
+{
+ std::ifstream file1(filename1, std::ifstream::binary);
+
+ std::istreambuf_iterator<char> begin1(file1);
+ std::istreambuf_iterator<char> begin2(file2);
+ return std::equal(begin1, std::istreambuf_iterator<char>(), begin2);
+}
+
+std::istream &safe_get_line(std::istream &is, std::string &t)
+{
+ t.clear();
+
+ // The characters in the stream are read one-by-one using a std::streambuf.
+ // That is faster than reading them one-by-one using the std::istream.
+ // Code that uses streambuf this way must be guarded by a sentry object.
+ // The sentry object performs various tasks,
+ // such as thread synchronization and updating the stream state.
+
+ std::istream::sentry se(is, true);
+ std::streambuf * sb = is.rdbuf();
+
+ for (;;) {
+ int c = sb->sbumpc();
+ switch (c) {
+ case '\n':
+ return is;
+ case '\r':
+ if (sb->sgetc() == '\n')
+ sb->sbumpc();
+ return is;
+ case std::streambuf::traits_type::eof():
+ // Also handle the case when the last line has no line ending
+ if (t.empty())
+ is.setstate(std::ios::eofbit);
+ return is;
+ default:
+ t += (char) c;
+ }
+ }
+}
+
+bool compare_text_files(const std::string &filename1, const std::string &filename2)
+{
+ bool res = true;
+ std::ifstream file1(filename1, std::ifstream::binary);
+ std::ifstream file2(filename2, std::ifstream::binary);
+ std::string s1, s2;
+
+ while (res) {
+ if (file1.eof() && file2.eof())
+ break;
+ safe_get_line(file1, s1);
+ safe_get_line(file2, s2);
+ if (s1 != s2)
+ res = false;
+ }
+
+ return res;
+}
+
+bool compare_text_files(const std::string &filename1, std::istream &file2)
+{
+ bool res = true;
+ std::ifstream file1(filename1, std::ifstream::binary);
+ std::string s1, s2;
+ file2.seekg(0);
+
+ while (res) {
+ if (file1.eof() && file2.eof())
+ break;
+ safe_get_line(file1, s1);
+ safe_get_line(file2, s2);
+ if (s1 != s2)
+ res = false;
+ }
+
+ return res;
+}
diff --git a/tests/src/exception-tests.cpp b/tests/src/exception-tests.cpp
new file mode 100644
index 0000000..318b2b8
--- /dev/null
+++ b/tests/src/exception-tests.cpp
@@ -0,0 +1,331 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ */
+
+#include "sexp-tests.h"
+
+using namespace sexp;
+
+namespace {
+class ExceptionTests : public testing::Test {
+ protected:
+ static void do_scan_with_exception(const char *str_in, const char *msg)
+ {
+ try {
+ std::istringstream iss(str_in);
+ sexp_input_stream_t is(&iss);
+ is.set_byte_size(8)->get_char()->scan_object();
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), msg);
+ }
+ }
+};
+
+TEST_F(ExceptionTests, UnexpectedEof)
+{
+ do_scan_with_exception("(4:This2:is1:a4:test",
+ "SEXP ERROR: unexpected end of file at position 20");
+}
+
+TEST_F(ExceptionTests, UnexpectedCharacter4bit)
+{
+ do_scan_with_exception(
+ "(4:This2:is1:a4:test #)",
+ "SEXP ERROR: character ')' found in 4-bit coding region at position 22");
+}
+
+TEST_F(ExceptionTests, IllegalCharacter)
+{
+ do_scan_with_exception("(This is a test ?)",
+ "SEXP ERROR: illegal character '?' (0x3f) at position 16");
+}
+
+TEST_F(ExceptionTests, UnexpectedEofAfterQoute)
+{
+ do_scan_with_exception("(\")\n", "SEXP ERROR: unexpected end of file at position 4");
+}
+
+TEST_F(ExceptionTests, IllegalCharacterBase64)
+{
+ do_scan_with_exception("(Test {KDQ6VGhpczI6aXMxOmE0OnRlc3Qq})",
+ "SEXP ERROR: illegal character '}' (0x7d) at position 35");
+}
+
+TEST_F(ExceptionTests, InvalidHex)
+{
+ do_scan_with_exception("(\"\\x1U\")",
+ "SEXP ERROR: Hex character \\x1... too short at position 5");
+}
+
+TEST_F(ExceptionTests, InvalidOctal)
+{
+ do_scan_with_exception("(\"\\12U\")",
+ "SEXP ERROR: Octal character \\12... too short at position 5");
+}
+
+TEST_F(ExceptionTests, TooBigOctal)
+{
+ do_scan_with_exception("(\"\\666U\")",
+ "SEXP ERROR: Octal character \\666... too big at position 5");
+}
+
+TEST_F(ExceptionTests, InvalidEscape)
+{
+ do_scan_with_exception("(\"\\?\")",
+ "SEXP ERROR: Unknown escape sequence \\? at position 3");
+}
+
+TEST_F(ExceptionTests, StringTooShortQuoted)
+{
+ do_scan_with_exception(
+ "(4\"ABC\")",
+ "SEXP ERROR: Declared length was 4, but quoted string ended too early at position 6");
+}
+
+TEST_F(ExceptionTests, StringTooShortBase64)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::warning);
+ do_scan_with_exception("(8|NDpBQkNE|)",
+ "SEXP WARNING: Base64 string has length 6 different than declared "
+ "length 8 at position 12");
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+}
+
+TEST_F(ExceptionTests, StringTooShortHex)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::warning);
+ do_scan_with_exception(
+ "(8#AAABFCAD#)",
+ "SEXP WARNING: Hex string has length 4 different than declared length 8 at position 12");
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+}
+
+TEST_F(ExceptionTests, StringBadLength)
+{
+ do_scan_with_exception("(1A:AAABFCAD)",
+ "SEXP ERROR: illegal character 'A' (0x41) at position 2");
+}
+
+TEST_F(ExceptionTests, DecimalTooLong)
+{
+ do_scan_with_exception("(1234567890:AAABFCAD)",
+ "SEXP ERROR: Decimal number is too long at position 11");
+}
+
+TEST_F(ExceptionTests, Base64CurlyBracket)
+{
+ // "ey..." in base64 encoding translates to "{..."
+ do_scan_with_exception("({ey})", "SEXP ERROR: illegal character '{' (0x7b) at position 3");
+}
+
+TEST_F(ExceptionTests, UnusedBits)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::warning);
+ do_scan_with_exception(
+ "(Test |AABBCCDD11|)",
+ "SEXP WARNING: 6-bit region ended with 4 unused bits left-over at position 17");
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+}
+
+TEST_F(ExceptionTests, NotAListWhenExpected)
+{
+ try {
+ std::istringstream iss(
+ "|d738/4ghP9rFZ0gAIYZ5q9y6iskDJwASi5rEQpEQq8ZyMZeIZzIAR2I5iGE=|");
+ sexp_input_stream_t is(&iss);
+
+ sexp_list_t a_list;
+ a_list.parse(is.set_byte_size(8)->get_char());
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(),
+ "SEXP ERROR: character '|' found where '(' was expected at position 0");
+ }
+}
+
+TEST_F(ExceptionTests, InvalidByteSizeAndMode)
+{
+ try {
+ std::istringstream iss("(3:a\011c)");
+ sexp_input_stream_t is(&iss);
+ std::shared_ptr<sexp_object_t> obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.change_output_byte_size(4, sexp_output_stream_t::advanced)->print_advanced(obj);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(
+ e.what(),
+ "SEXP ERROR: Can't print in advanced mode with restricted output character set");
+ }
+}
+
+TEST_F(ExceptionTests, SexpWarning)
+{
+ testing::internal::CaptureStdout();
+ sexp::sexp_exception_t::set_interactive(true);
+ sexp_error(sexp_exception_t::warning, "Test warning", 0, 0, 200);
+ std::string output = testing::internal::GetCapturedStdout();
+ EXPECT_EQ(output, "\n*** SEXP WARNING: Test warning at position 200 ***\n");
+ sexp::sexp_exception_t::set_interactive(false);
+}
+
+static void do_parse_list_from_string(const char *str)
+{
+ std::istringstream iss(str);
+ sexp_input_stream_t is(&iss);
+ sexp_list_t lst;
+ lst.parse(is.set_byte_size(8)->get_char());
+}
+
+static void do_parse_list_from_string_with_limit(const char *str, size_t m_depth)
+{
+ std::istringstream iss(str);
+ sexp_input_stream_t is(&iss, m_depth);
+ sexp_list_t lst;
+ lst.parse(is.set_byte_size(8)->get_char());
+}
+
+TEST_F(ExceptionTests, MaxDepthParse)
+{
+ const char *depth_1 = "(sexp_list_1)";
+ const char *depth_4 = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4))))";
+ const char *depth_4e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 ())))";
+ const char *depth_5 =
+ "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 (sexp_list_5)))))";
+ const char *depth_5e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 ()))))";
+
+ do_parse_list_from_string(depth_1);
+ do_parse_list_from_string(depth_4);
+ do_parse_list_from_string(depth_4e);
+ do_parse_list_from_string(depth_5);
+ do_parse_list_from_string(depth_5e);
+
+ do_parse_list_from_string_with_limit(depth_1, 4);
+ do_parse_list_from_string_with_limit(depth_4, 4);
+ do_parse_list_from_string_with_limit(depth_4e, 4);
+
+ try {
+ do_parse_list_from_string_with_limit(depth_5, 4);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(
+ e.what(),
+ "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded at position 53");
+ }
+
+ try {
+ do_parse_list_from_string_with_limit(depth_5e, 4);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(
+ e.what(),
+ "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded at position 53");
+ }
+}
+
+static void do_print_list_from_string(const char *str, bool advanced, size_t m_depth = 0)
+{
+ std::istringstream iss(str);
+ sexp_input_stream_t is(&iss);
+ sexp_list_t lst;
+ lst.parse(is.set_byte_size(8)->get_char());
+
+ std::ostringstream oss(str);
+ sexp_output_stream_t os(&oss, m_depth);
+ if (advanced)
+ lst.print_advanced(&os);
+ else
+ lst.print_canonical(&os);
+}
+
+TEST_F(ExceptionTests, MaxDepthPrintAdvanced)
+{
+ const char *depth_1 = "(sexp_list_1)";
+ const char *depth_4 = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4))))";
+ const char *depth_4e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 ())))";
+ const char *depth_5 =
+ "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 (sexp_list_5)))))";
+ const char *depth_5e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 ()))))";
+
+ do_print_list_from_string(depth_1, true);
+ do_print_list_from_string(depth_4, true);
+ do_print_list_from_string(depth_4e, true);
+ do_print_list_from_string(depth_5, true);
+ do_print_list_from_string(depth_5e, true);
+
+ do_print_list_from_string(depth_1, true, 4);
+ do_print_list_from_string(depth_4, true, 4);
+ do_print_list_from_string(depth_4e, true, 4);
+
+ try {
+ do_print_list_from_string(depth_5, true, 4);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded");
+ }
+
+ try {
+ do_print_list_from_string(depth_5e, true, 4);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded");
+ }
+}
+
+TEST_F(ExceptionTests, MaxDepthPrintCanonical)
+{
+ const char *depth_1 = "(sexp_list_1)";
+ const char *depth_4 = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4))))";
+ const char *depth_4e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 ())))";
+ const char *depth_5 =
+ "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 (sexp_list_5)))))";
+ const char *depth_5e = "(sexp_list_1 (sexp_list_2 (sexp_list_3 (sexp_list_4 ()))))";
+
+ do_print_list_from_string(depth_1, false);
+ do_print_list_from_string(depth_4, false);
+ do_print_list_from_string(depth_4e, false);
+ do_print_list_from_string(depth_5, false);
+ do_print_list_from_string(depth_5e, false);
+
+ do_print_list_from_string(depth_1, false, 4);
+ do_print_list_from_string(depth_4, false, 4);
+ do_print_list_from_string(depth_4e, false, 4);
+
+ try {
+ do_print_list_from_string(depth_5, false, 4);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded");
+ }
+
+ try {
+ do_print_list_from_string(depth_5e, false, 4);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), "SEXP ERROR: Maximum allowed SEXP list depth (4) is exceeded");
+ }
+}
+
+} // namespace
diff --git a/tests/src/g10-compat-tests.cpp b/tests/src/g10-compat-tests.cpp
new file mode 100644
index 0000000..62f1180
--- /dev/null
+++ b/tests/src/g10-compat-tests.cpp
@@ -0,0 +1,51 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ */
+
+#include "sexp-tests.h"
+
+using namespace sexp;
+
+namespace {
+class G10CompatTests : public testing::Test {
+};
+
+TEST_F(G10CompatTests, Canonical)
+{
+ std::string keyfile(sexp_samples_folder + "/compat/g10/canonical.key");
+ std::ifstream ifs(keyfile, std::ifstream::binary);
+ EXPECT_FALSE(ifs.fail());
+
+ if (!ifs.fail()) {
+ sexp_input_stream_t is(&ifs);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.print_canonical(obj);
+
+ std::istringstream iss(oss.str(), std::ios_base::binary);
+ EXPECT_TRUE(compare_binary_files(keyfile, iss));
+ }
+}
+
+} // namespace
diff --git a/tests/src/g23-compat-tests.cpp b/tests/src/g23-compat-tests.cpp
new file mode 100644
index 0000000..3a2e756
--- /dev/null
+++ b/tests/src/g23-compat-tests.cpp
@@ -0,0 +1,139 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ */
+
+#include "sexp-tests.h"
+#include "sexpp/ext-key-format.h"
+
+using namespace sexp;
+using namespace ext_key_format;
+
+using ::testing::UnitTest;
+
+namespace {
+class G23CompatTests : public testing::Test {
+ protected:
+ static void scan_and_check_correct(const char *fn)
+ {
+ std::ifstream ifs(sexp_samples_folder + "/compat/g23/" + fn, std::ifstream::binary);
+ bool r = ifs.fail();
+ EXPECT_FALSE(r);
+
+ if (!ifs.fail()) {
+ ext_key_input_stream_t is(&ifs);
+ extended_private_key_t extended_key;
+ is.scan(extended_key);
+ EXPECT_EQ(extended_key.fields.size(), 2);
+ EXPECT_EQ(extended_key.fields.count("Created"), 1);
+ EXPECT_EQ(extended_key.fields.count("creaTed"), 1);
+ EXPECT_EQ(extended_key.fields.count("something"), 0);
+
+ auto search = extended_key.fields.find("Created");
+ if (search != extended_key.fields.end()) {
+ EXPECT_EQ(search->second, "20221130T160847");
+ }
+ }
+ }
+};
+
+TEST_F(G23CompatTests, G10Test)
+{
+ std::ifstream ifs(sexp_samples_folder + "/compat/g10/canonical.key",
+ std::ifstream::binary);
+ bool r = ifs.fail();
+ EXPECT_FALSE(r);
+
+ if (!ifs.fail()) {
+ ext_key_input_stream_t is(&ifs);
+ extended_private_key_t extended_key;
+ is.scan(extended_key);
+ EXPECT_EQ(extended_key.fields.size(), 0);
+ }
+}
+
+// Correct extended key format
+TEST_F(G23CompatTests, G23Correct)
+{
+ scan_and_check_correct("correct.key");
+}
+
+// Correct extended key format, no terminating end of line
+TEST_F(G23CompatTests, G23CorrectNoEol)
+{
+ scan_and_check_correct("correct_no_eol.key");
+}
+
+// Correct extended key format, with a comment
+TEST_F(G23CompatTests, G23CorrectWithComment)
+{
+ scan_and_check_correct("correct_with_comment.key");
+}
+
+// Correct extended key format, with an empty line (which is comment a well)
+TEST_F(G23CompatTests, G23CorrectWithTwoEmptyLines)
+{
+ scan_and_check_correct("correct_with_two_empty_lines.key");
+}
+
+// Correct extended key format, with two empty linea
+TEST_F(G23CompatTests, G23CorrectWithEmptyLine)
+{
+ scan_and_check_correct("correct_with_empty_line.key");
+}
+
+// Correct extended key format, witg windows line endings
+TEST_F(G23CompatTests, G23CorrectWithWindowsEol)
+{
+ scan_and_check_correct("correct_with_windows_eol.key");
+}
+
+// Correct extended key format, with a comment at the end of file
+TEST_F(G23CompatTests, G23CorrectWithCommentAtEof)
+{
+ scan_and_check_correct("correct_with_comment_at_eof.key");
+}
+
+// Correct extended key format, with multiple fields of the same name
+TEST_F(G23CompatTests, G23CorrectWithMultFields)
+{
+ std::ifstream ifs(sexp_samples_folder + "/compat/g23/correct_mult_fields.key",
+ std::ifstream::binary);
+ bool r = ifs.fail();
+ EXPECT_FALSE(r);
+
+ if (!ifs.fail()) {
+ ext_key_input_stream_t is(&ifs);
+ extended_private_key_t extended_key;
+ extended_key.parse(is);
+ EXPECT_EQ(extended_key.fields.size(), 4);
+ EXPECT_EQ(extended_key.fields.count("Created"), 1);
+ EXPECT_EQ(extended_key.fields.count("Description"), 3);
+ EXPECT_EQ(extended_key.fields.count("something"), 0);
+
+ auto search = extended_key.fields.find("Description");
+ if (search != extended_key.fields.end()) {
+ EXPECT_EQ(search->second, "RSA/RSA");
+ }
+ }
+}
+
+} // namespace
diff --git a/tests/src/g23-exception-tests.cpp b/tests/src/g23-exception-tests.cpp
new file mode 100644
index 0000000..4f4413d
--- /dev/null
+++ b/tests/src/g23-exception-tests.cpp
@@ -0,0 +1,108 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ */
+
+#include "sexp-tests.h"
+#include "sexpp/ext-key-format.h"
+
+using namespace sexp;
+using namespace ext_key_format;
+
+using ::testing::UnitTest;
+
+namespace {
+class G23ExceptionTests : public testing::Test {
+ protected:
+ static void do_scan_ex(const char *fn, const char *msg)
+ {
+ std::ifstream ifs(sexp_samples_folder + "/compat/g23/" + fn, std::ifstream::binary);
+ EXPECT_FALSE(ifs.fail());
+ if (!ifs.fail()) {
+ try {
+ ext_key_input_stream_t is(&ifs);
+ extended_private_key_t extended_key;
+ is.scan(extended_key);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), msg);
+ }
+ }
+ }
+};
+
+// Malformed extended key format, line break inside name
+TEST_F(G23ExceptionTests, G23MalformedNameBreak)
+{
+ do_scan_ex("malformed_name_break.key",
+ "EXTENDED KEY FORMAT ERROR: unexpected end of line at position 5");
+}
+
+// Malformed extended key format, eof break inside name
+TEST_F(G23ExceptionTests, G23MalformedNameEof)
+{
+ do_scan_ex("malformed_name_eof.key",
+ "EXTENDED KEY FORMAT ERROR: unexpected end of file at position 2800");
+}
+
+// Malformed extended key format, invalid character name
+TEST_F(G23ExceptionTests, G23MalformedInvalidNameChar)
+{
+ do_scan_ex(
+ "malformed_invalid_name_char.key",
+ "EXTENDED KEY FORMAT ERROR: unexpected character '@' (0x40) found in a name field "
+ "at position 28");
+}
+
+// Malformed extended key format, invalid character name
+TEST_F(G23ExceptionTests, G23MalformedInvalidNameFirstChar)
+{
+ do_scan_ex(
+ "malformed_invalid_name_first_char.key",
+ "EXTENDED KEY FORMAT ERROR: unexpected character '1' (0x31) found starting a name field "
+ "at position 21");
+}
+
+// Malformed extended key format, no key field
+TEST_F(G23ExceptionTests, G23MalformedNoKey)
+{
+ do_scan_ex("malformed_no_key.key",
+ "EXTENDED KEY FORMAT ERROR: missing mandatory 'key' field at position 2819");
+}
+
+// Malformed extended key format, two key fields
+TEST_F(G23ExceptionTests, G23MalformedTwoKeys)
+{
+ do_scan_ex("malformed_two_keys.key",
+ "EXTENDED KEY FORMAT ERROR: 'key' field must occur only once at position 2822");
+}
+
+TEST_F(G23ExceptionTests, G23Warning)
+{
+ testing::internal::CaptureStdout();
+ sexp::sexp_exception_t::set_interactive(true);
+ ext_key_error(sexp_exception_t::warning, "Test warning", 0, 0, 200);
+ std::string output = testing::internal::GetCapturedStdout();
+ EXPECT_EQ(output, "\n*** EXTENDED KEY FORMAT WARNING: Test warning at position 200 ***\n");
+ sexp::sexp_exception_t::set_interactive(false);
+}
+
+} // namespace
diff --git a/tests/src/primitives-tests.cpp b/tests/src/primitives-tests.cpp
new file mode 100644
index 0000000..0689d27
--- /dev/null
+++ b/tests/src/primitives-tests.cpp
@@ -0,0 +1,394 @@
+/**
+ *
+ * Copyright 2021-2023 Ribose Inc. (https://www.ribose.com)
+ *
+ * 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.
+ *
+ */
+
+#include "sexp-tests.h"
+
+using namespace sexp;
+
+namespace {
+class PrimitivesTests : public testing::Test {
+ protected:
+ static void do_test_advanced(const char *str_in, const char *str_out = nullptr)
+ {
+ std::istringstream iss(str_in);
+ sexp_input_stream_t is(&iss);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.print_advanced(obj);
+ const char *sample = str_out == nullptr ? str_in : str_out;
+ EXPECT_EQ(oss.str(), sample);
+ }
+
+ static void do_test_canonical(const char *str_in, const char *str_out = nullptr)
+ {
+ std::istringstream iss(str_in);
+ sexp_input_stream_t is(&iss);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.print_canonical(obj);
+ const char *sample = str_out == nullptr ? str_in : str_out;
+ EXPECT_EQ(oss.str(), sample);
+ }
+};
+
+TEST_F(PrimitivesTests, EmptyList)
+{
+ do_test_canonical("( )", "()");
+ do_test_advanced("( )", "()");
+}
+
+TEST_F(PrimitivesTests, EmptyString)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+ do_test_canonical("(\"\")", "(0:)");
+ do_test_advanced("(\"\")", "(\"\")");
+}
+
+TEST_F(PrimitivesTests, String)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+ do_test_canonical("(ab)", "(2:ab)");
+ do_test_advanced("(ab)", "(ab)");
+}
+
+TEST_F(PrimitivesTests, QuotedStringWithOctal)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+ do_test_canonical("\"ab\\015\"", "3:ab\r");
+ do_test_advanced("\"ab\\015\"", "#61620D#");
+}
+
+TEST_F(PrimitivesTests, QuotedStringWithEscape)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+ do_test_canonical("\"ab\\tc\"", "4:ab\tc");
+ do_test_advanced("4:ab\tc", "#61620963#");
+}
+
+TEST_F(PrimitivesTests, HexString)
+{
+ sexp::sexp_exception_t::set_verbosity(sexp::sexp_exception_t::error);
+ do_test_canonical("#616263#", "3:abc");
+ do_test_advanced("#616263#", "abc");
+}
+
+TEST_F(PrimitivesTests, ListList)
+{
+ do_test_canonical("(string-level-1 (string-level-2) )",
+ "(14:string-level-1(14:string-level-2))");
+ do_test_advanced("(string-level-1 (string-level-2) )",
+ "(string-level-1 (string-level-2))");
+}
+
+TEST_F(PrimitivesTests, Base64Ofoctet_t)
+{
+ do_test_canonical("|YWJj|", "3:abc");
+ do_test_advanced("|YWJj|", "abc");
+}
+
+TEST_F(PrimitivesTests, Base64OfVerbatium)
+{
+ do_test_canonical("{MzphYmM=}", "3:abc");
+ do_test_advanced("{MzphYmM=}", "abc");
+}
+
+TEST_F(PrimitivesTests, MultilineLinux)
+{
+ do_test_canonical("\"abcd\\\nef\"", "6:abcdef");
+ do_test_advanced("\"abcd\\\nef\"", "abcdef");
+}
+
+TEST_F(PrimitivesTests, MultilineMac)
+{
+ do_test_canonical("\"abcd\\\ref\"", "6:abcdef");
+ do_test_advanced("\"abcd\\\ref\"", "abcdef");
+}
+
+TEST_F(PrimitivesTests, MultilineWin)
+{
+ do_test_canonical("\"abcd\\\r\nef\"", "6:abcdef");
+ do_test_advanced("\"abcd\\\r\nef\"", "abcdef");
+}
+
+TEST_F(PrimitivesTests, MultilineBsd)
+{
+ do_test_canonical("\"abcd\\\n\ref\"", "6:abcdef");
+ do_test_advanced("\"abcd\\\n\ref\"", "abcdef");
+}
+
+TEST_F(PrimitivesTests, Wrap)
+{
+ const char *reallyLong = "(a (b (c ddddddddddddddd"
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "ddddddd)))";
+ const char *stillLong = "(1:a(1:b(1:c169:ddddddddd"
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "ddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "ddddddddddddd)))";
+ const char *broken = "(a\n"
+ " (b\n"
+ " (c\n"
+ " "
+ "\"ddddddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "dddddddddddddddd\\\n"
+ "ddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
+ "dddddddddddddddddd\\\n"
+ "ddddddddddddddddddddddddddd\")))";
+ do_test_canonical(reallyLong, stillLong);
+ do_test_advanced(reallyLong, broken);
+}
+
+TEST_F(PrimitivesTests, Escapes)
+{
+ do_test_canonical("(\"\\b\\t\\v\\n\\f\\r\\\"\\'\\\\\")", "(9:\b\t\v\n\f\r\"'\\)");
+ do_test_advanced("(\"\\b\\t\\v\\n\\f\\r\\\"\\'\\\\\")", "(|CAkLCgwNIidc|)");
+
+ do_test_canonical("(\"\\040\\041\\042\\043\\044\")", "(5: !\"#$)");
+ do_test_advanced("(\"\\065\\061\\062\\063\\064\")", "(\"51234\")");
+
+ do_test_canonical("(\"\\x40\\x41\\x42\\x43\\x44\")", "(5:@ABCD)");
+ do_test_advanced("(\"\\x65\\x61\\x62\\x63\\x64\")", "(eabcd)");
+}
+
+TEST_F(PrimitivesTests, at4rnp)
+{
+ const char *str_in = "(rnp_block (rnp_list1 rnp_list2))";
+
+ std::istringstream iss(str_in);
+ sexp_input_stream_t is(&iss);
+
+ sexp_list_t lst;
+ lst.parse(is.set_byte_size(8)->get_char());
+
+ EXPECT_EQ(lst.sexp_list_at(0), nullptr);
+ EXPECT_NE(lst.sexp_list_at(1), nullptr);
+
+ EXPECT_NE(lst.sexp_string_at(0), nullptr);
+ EXPECT_EQ(lst.sexp_string_at(1), nullptr);
+
+ const sexp_object_t *obj = lst.sexp_list_at(1);
+
+ if (obj != nullptr) {
+ EXPECT_EQ(obj->sexp_list_at(0), nullptr);
+ EXPECT_EQ(obj->sexp_list_at(1), nullptr);
+ }
+
+ const sexp_string_t *sstr = lst.sexp_string_at(0);
+ EXPECT_STREQ(reinterpret_cast<const char *>(sstr->get_string().c_str()), "rnp_block");
+}
+
+TEST_F(PrimitivesTests, eq4rnp)
+{
+ const char *str_in = "(rnp_block (rnp_list1 rnp_list2))";
+
+ std::istringstream iss(str_in);
+ sexp_input_stream_t is(&iss);
+
+ sexp_list_t lst;
+ lst.parse(is.set_byte_size(8)->get_char());
+
+ EXPECT_TRUE(*lst.at(0) == "rnp_block");
+ EXPECT_FALSE(*lst.at(0) == "not_rnp_block");
+ EXPECT_FALSE(*lst.at(1) == "rnp_block");
+ EXPECT_FALSE(*lst.at(1) == "not_rnp_block");
+
+ EXPECT_TRUE(*lst.sexp_string_at(0) == "rnp_block");
+ EXPECT_FALSE(*lst.sexp_string_at(0) == "not_rnp_block");
+ EXPECT_TRUE(*lst.sexp_simple_string_at(0) == "rnp_block");
+ EXPECT_FALSE(*lst.sexp_simple_string_at(0) == "not_rnp_block");
+
+ EXPECT_TRUE(*lst.sexp_list_at(1)->at(0) == "rnp_list1");
+ EXPECT_TRUE(*lst.sexp_list_at(1)->sexp_string_at(1) == "rnp_list2");
+
+ EXPECT_TRUE(lst.sexp_string_at(0) == std::string("rnp_block"));
+ EXPECT_FALSE(lst.sexp_string_at(0) == std::string("not_rnp_block"));
+ EXPECT_TRUE(lst.sexp_simple_string_at(0) == std::string("rnp_block"));
+ EXPECT_FALSE(lst.sexp_simple_string_at(0) == std::string("not_rnp_block"));
+}
+
+TEST_F(PrimitivesTests, ne4rnp)
+{
+ const char *str_in = "(rnp_block (rnp_list1 rnp_list2))";
+
+ std::istringstream iss(str_in);
+ sexp_input_stream_t is(&iss);
+
+ sexp_list_t lst;
+ lst.parse(is.set_byte_size(8)->get_char());
+
+ EXPECT_FALSE(*lst.at(0) != "rnp_block");
+ EXPECT_TRUE(*lst.at(0) != "not_rnp_block");
+ EXPECT_TRUE(*lst.at(1) != "rnp_block");
+ EXPECT_TRUE(*lst.at(1) != "not_rnp_block");
+
+ EXPECT_FALSE(*lst.sexp_string_at(0) != "rnp_block");
+ EXPECT_TRUE(*lst.sexp_string_at(0) != "not_rnp_block");
+ EXPECT_FALSE(*lst.sexp_simple_string_at(0) != "rnp_block");
+ EXPECT_TRUE(*lst.sexp_simple_string_at(0) != "not_rnp_block");
+
+ EXPECT_FALSE(*lst.sexp_list_at(1)->at(0) != "rnp_list1");
+ EXPECT_FALSE(*lst.sexp_list_at(1)->sexp_string_at(1) != "rnp_list2");
+
+ EXPECT_FALSE(lst.sexp_string_at(0) != std::string("rnp_block"));
+ EXPECT_TRUE(lst.sexp_string_at(0) != std::string("not_rnp_block"));
+ EXPECT_FALSE(lst.sexp_simple_string_at(0) != std::string("rnp_block"));
+ EXPECT_TRUE(lst.sexp_simple_string_at(0) != std::string("not_rnp_block"));
+}
+
+TEST_F(PrimitivesTests, u4rnp)
+{
+ const char *str_in1 = "(unsigned_value \"12345\")";
+ const char *str_in2 = "(14:unsigned_value5:54321)";
+
+ std::istringstream iss1(str_in1);
+ std::istringstream iss2(str_in2);
+
+ sexp_input_stream_t is(&iss1);
+ sexp_list_t lst;
+ lst.parse(is.set_byte_size(8)->get_char());
+ EXPECT_EQ(lst.sexp_string_at(1)->as_unsigned(), 12345);
+
+ lst.clear();
+ lst.parse(is.set_input(&iss2)->set_byte_size(8)->get_char());
+ EXPECT_EQ(lst.sexp_string_at(1)->as_unsigned(), 54321);
+}
+
+TEST_F(PrimitivesTests, proInheritance)
+{
+ sexp_list_t lst;
+ EXPECT_FALSE(lst.is_sexp_string());
+ EXPECT_TRUE(lst.is_sexp_list());
+ EXPECT_EQ(lst.sexp_string_view(), nullptr);
+ EXPECT_EQ(lst.sexp_list_view(), &lst);
+ EXPECT_EQ(lst.as_unsigned(), std::numeric_limits<uint32_t>::max());
+ EXPECT_EQ(lst.sexp_list_at(0), nullptr);
+ EXPECT_EQ(lst.sexp_string_at(0), nullptr);
+ EXPECT_EQ(lst.sexp_simple_string_at(0), nullptr);
+
+ sexp_string_t str;
+ EXPECT_FALSE(str.is_sexp_list());
+ EXPECT_TRUE(str.is_sexp_string());
+ EXPECT_EQ(str.sexp_string_view(), &str);
+ EXPECT_EQ(str.sexp_list_view(), nullptr);
+ EXPECT_EQ(str.sexp_list_at(0), nullptr);
+ EXPECT_EQ(str.sexp_string_at(0), nullptr);
+ EXPECT_EQ(str.sexp_simple_string_at(0), nullptr);
+}
+
+TEST_F(PrimitivesTests, DisplayHint)
+{
+ do_test_canonical("(URL [URI]www.ribose.com)", "(3:URL[3:URI]14:www.ribose.com)");
+ do_test_advanced("(3:URL[3:URI]14:www.ribose.com)", "(URL [URI]www.ribose.com)");
+}
+
+TEST_F(PrimitivesTests, scanToEof)
+{
+ const char *str_in = "ABCD";
+
+ std::istringstream iss(str_in);
+ sexp_input_stream_t is(&iss);
+
+ auto object = is.scan_to_eof();
+ EXPECT_TRUE(object->is_sexp_string());
+
+ is.set_byte_size(4);
+ EXPECT_EQ(is.get_byte_size(), 4);
+
+ EXPECT_EQ(is.get_char(), &is);
+ EXPECT_EQ(is.get_byte_size(), 8);
+}
+
+TEST_F(PrimitivesTests, ChangeOutputByteSizeTest)
+{
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+
+ EXPECT_EQ(os.change_output_byte_size(8, sexp_output_stream_t::advanced), &os);
+
+ try {
+ os.change_output_byte_size(7, sexp_output_stream_t::advanced);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), "SEXP ERROR: Illegal output base 7");
+ }
+
+ EXPECT_EQ(os.change_output_byte_size(4, sexp_output_stream_t::advanced), &os);
+
+ try {
+ os.change_output_byte_size(6, sexp_output_stream_t::advanced);
+ FAIL() << "sexp::sexp_exception_t expected but has not been thrown";
+ } catch (sexp::sexp_exception_t &e) {
+ EXPECT_STREQ(e.what(), "SEXP ERROR: Illegal change of output byte size from 4 to 6");
+ }
+}
+
+TEST_F(PrimitivesTests, FlushTest)
+{
+ std::ostringstream oss1(std::ios_base::binary);
+ std::ostringstream oss2(std::ios_base::binary);
+ sexp_output_stream_t os(&oss1);
+
+ EXPECT_EQ(
+ os.change_output_byte_size(6, sexp_output_stream_t::advanced)->print_decimal(1)->flush(),
+ &os);
+ EXPECT_EQ(oss1.str(), "MQ==");
+ os.set_output(&oss2)
+ ->change_output_byte_size(6, sexp_output_stream_t::advanced)
+ ->set_max_column(2)
+ ->print_decimal(2)
+ ->flush();
+ EXPECT_EQ(oss2.str(), "Mg\n==");
+}
+
+TEST_F(PrimitivesTests, ListWrapTest)
+{
+ std::istringstream iss("(abc)");
+ sexp_input_stream_t is(&iss);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.set_max_column(5)->print_advanced(obj);
+ EXPECT_EQ(oss.str(), "(abc\n )");
+}
+
+TEST_F(PrimitivesTests, EnsureHexTest)
+{
+ std::istringstream iss("(3:a\011c)");
+ sexp_input_stream_t is(&iss);
+ const auto obj = is.set_byte_size(8)->get_char()->scan_object();
+
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.print_advanced(obj);
+ EXPECT_EQ(oss.str(), "(#610963#)");
+}
+
+} // namespace