diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/include/sexp-tests.h | 42 | ||||
-rwxr-xr-x | tests/scripts/tests.sh | 191 | ||||
-rw-r--r-- | tests/src/baseline-tests.cpp | 115 | ||||
-rw-r--r-- | tests/src/compare-files.cpp | 126 | ||||
-rw-r--r-- | tests/src/exception-tests.cpp | 331 | ||||
-rw-r--r-- | tests/src/g10-compat-tests.cpp | 51 | ||||
-rw-r--r-- | tests/src/g23-compat-tests.cpp | 139 | ||||
-rw-r--r-- | tests/src/g23-exception-tests.cpp | 108 | ||||
-rw-r--r-- | tests/src/primitives-tests.cpp | 394 |
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 |