From 22d117f785a11926353c50cb0ab42e4d9ec848eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oona=20R=C3=A4is=C3=A4nen?= Date: Wed, 29 May 2024 06:52:28 +0300 Subject: [PATCH] Introduce unit tests Add some basic, mostly text-related unit tests using Catch2 Also, add -o option --- .github/workflows/build.yml | 9 ++- CHANGES.md | 1 + README.md | 17 +++-- meson.build | 14 +++- src/channel.cc | 29 +++++--- src/channel.h | 6 +- src/groups.cc | 30 ++++---- src/groups.h | 23 +++--- src/options.cc | 17 ++++- src/redsea.cc | 54 +++++++------- test/test.cc | 142 ++++++++++++++++++++++++++++++++++++ test/test_helpers.h | 75 +++++++++++++++++++ 12 files changed, 339 insertions(+), 78 deletions(-) create mode 100644 test/test.cc create mode 100644 test/test_helpers.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5b79db7..7c9e832 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: build on: push: - branches: [ master ] + branches: [ master, meson ] pull_request: branches: [ master ] @@ -21,7 +21,7 @@ jobs: - name: compile run: cd build && meson compile - macos-build: + macos-build-and-test: runs-on: macos-latest steps: @@ -32,6 +32,8 @@ jobs: run: meson setup build - name: compile run: cd build && meson compile + - name: test + run: cd build && meson test windows-msys2-mingw-build: runs-on: windows-latest @@ -107,7 +109,8 @@ jobs: ./bootstrap.sh && ./configure --prefix=/usr && make && make install - # Cygwin does not allow underscore variables that start with an uppercase when compiling with gcc + # Cygwin does not allow underscore variables that start with an uppercase when + # compiling with gcc - name: Patch liquid-dsp shell: C:\cygwin\bin\bash.exe -eo pipefail '{0}' run: perl -i -p -e 's/(?<=\s)(_[A-Z])(?=[,\)])/\L\1__/g' /usr/include/liquid/liquid.h diff --git a/CHANGES.md b/CHANGES.md index 742f85b..1e3d584 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ * Migrate build system from autotools to meson (#90) * Add GitHub Workflows CI builds for macOS and Windows (MSYS2/MinGW + Cygwin) +* Add basic unit tests for hex input using Catch2 (#84) * Remove unmaintained build options for non-liquid, non-TMC builds * Add support for enhanced RadioText (eRT) * Add support for Long PS in Group 15A (#104) diff --git a/README.md b/README.md index d363757..40781ae 100644 --- a/README.md +++ b/README.md @@ -113,16 +113,20 @@ redsea -f WAVEFILE readable by libsndfile should work. -i, --input FORMAT Decode stdin as FORMAT (see the wiki for more info): - bits Unsynchronized ASCII bit stream (011010110...). - All characters but '0' and '1' are ignored. - hex RDS Spy hex format. - mpx Mono S16LE PCM-encoded MPX waveform (default). - tef Serial data from the TEF6686 tuner. + bits (-b) Unsynchronized ASCII bit stream (011010110...). + All characters but '0' and '1' are ignored. + hex (-h) RDS Spy hex format. + mpx Mono S16LE PCM-encoded MPX waveform (default). + tef Serial data from the TEF6686 tuner. -l, --loctable DIR Load TMC location table from a directory in TMC Exchange format. This option can be specified multiple times to load several location tables. +-o, --output FORMAT Print output groups as FORMAT: + hex (-x) RDS Spy hex format. + json Newline-delimited json (default). + -p, --show-partial Show some multi-group data even before they've been fully received (PS names, RadioText, alternative frequencies). partial_ will be prepended to their @@ -144,9 +148,6 @@ redsea -f WAVEFILE TMC. -v, --version Print version string and exit. - --x, --output-hex Output hex groups in the RDS Spy format, - suppressing JSON output. ``` ### Formatting and filtering the JSON output diff --git a/meson.build b/meson.build index dd4c97c..01027ed 100644 --- a/meson.build +++ b/meson.build @@ -54,7 +54,7 @@ else add_project_arguments('-std=c++14', language : 'cpp') endif -sources = [ +sources_no_main = [ 'ext/json/jsoncpp.cpp', 'src/block_sync.cc', 'src/channel.cc', @@ -64,7 +64,6 @@ sources = [ 'src/liquid_wrappers.cc', 'src/options.cc', 'src/rdsstring.cc', - 'src/redsea.cc', 'src/subcarrier.cc', 'src/tables.cc', 'src/tmc/tmc.cc', @@ -72,4 +71,13 @@ sources = [ 'src/util.cc' ] -executable('redsea', sources, dependencies: [iconv, sndfile, liquid]) +executable('redsea', [sources_no_main, 'src/redsea.cc'], dependencies: [iconv, sndfile, liquid]) + +### Unit tests ### + +catch2_dep = dependency('catch2-with-main', required: false) + +if catch2_dep.found() + test_exec = executable('redsea-test', [sources_no_main, 'test/test.cc'], dependencies: [ iconv, sndfile, liquid, catch2_dep ]) + test('Smoke tests', test_exec) +endif diff --git a/src/channel.cc b/src/channel.cc index c5a42bc..a97bfba 100644 --- a/src/channel.cc +++ b/src/channel.cc @@ -16,6 +16,9 @@ */ #include "src/channel.h" +#include +#include + #include "src/common.h" namespace redsea { @@ -29,10 +32,20 @@ namespace redsea { * bits, or groups. Usage of these inputs shouldn't be intermixed. * */ -Channel::Channel(const Options& options, int which_channel) : +Channel::Channel(const Options& options, int which_channel, std::ostream& output_stream = std::cout) : options_(options), which_channel_(which_channel), - block_stream_(options), station_(0x0000, options, which_channel, false) { + output_stream_(output_stream), + block_stream_(options), station_(options, which_channel) { +} + +// Used for testing (PI is already known) +Channel::Channel(const Options& options, std::ostream& output_stream, uint16_t pi) : + options_(options), + which_channel_(0), + output_stream_(output_stream), + block_stream_(options), station_(options, 0, pi) { + cached_pi_.update(pi); } void Channel::processBit(bool bit) { @@ -92,7 +105,7 @@ void Channel::processGroup(Group group) { const auto pi_status = cached_pi_.update(group.getPI()); switch (pi_status) { case CachedPI::Result::ChangeConfirmed: - station_ = Station(cached_pi_.get(), options_, which_channel_); + station_ = Station(options_, which_channel_, cached_pi_.get()); break; case CachedPI::Result::SpuriousChange: @@ -103,17 +116,15 @@ void Channel::processGroup(Group group) { } } - auto stream = options_.feed_thru ? &std::cerr : &std::cout; - if (options_.output_type == redsea::OutputType::Hex) { if (!group.isEmpty()) { - group.printHex(stream); + group.printHex(output_stream_); if (options_.timestamp) - *stream << ' ' << getTimePointString(group.getRxTime(), options_.time_format); - *stream << '\n' << std::flush; + output_stream_ << ' ' << getTimePointString(group.getRxTime(), options_.time_format); + output_stream_ << '\n' << std::flush; } } else { - station_.updateAndPrint(group, stream); + station_.updateAndPrint(group, output_stream_); } } diff --git a/src/channel.h b/src/channel.h index 398a164..5603551 100644 --- a/src/channel.h +++ b/src/channel.h @@ -17,6 +17,8 @@ #ifndef CHANNEL_H_ #define CHANNEL_H_ +#include + #include "src/block_sync.h" #include "src/common.h" #include "src/options.h" @@ -70,7 +72,8 @@ class CachedPI { class Channel { public: - Channel(const Options& options, int which_channel); + Channel(const Options& options, int which_channel, std::ostream& output_stream); + Channel(const Options& options, std::ostream& output_stream, uint16_t pi); void processBit(bool bit); void processBits(const BitBuffer& buffer); void processGroup(Group group); @@ -81,6 +84,7 @@ class Channel { private: Options options_; int which_channel_; + std::ostream& output_stream_; CachedPI cached_pi_{}; BlockStream block_stream_; Station station_; diff --git a/src/groups.cc b/src/groups.cc index 9c379e9..9aa36a6 100644 --- a/src/groups.cc +++ b/src/groups.cc @@ -194,19 +194,19 @@ void Group::setAverageBLER(float bler) { * Invalid blocks are replaced with "----". * */ -void Group::printHex(std::ostream* stream) const { - stream->fill('0'); - stream->setf(std::ios_base::uppercase); +void Group::printHex(std::ostream& stream) const { + stream.fill('0'); + stream.setf(std::ios_base::uppercase); for (eBlockNumber block_num : {BLOCK1, BLOCK2, BLOCK3, BLOCK4}) { const Block& block = blocks_[block_num]; if (block.is_received) - *stream << std::hex << std::setw(4) << block.data; + stream << std::hex << std::setw(4) << block.data; else - *stream << "----"; + stream << "----"; if (block_num != BLOCK4) - *stream << " "; + stream << " "; } } @@ -215,11 +215,8 @@ void Group::printHex(std::ostream* stream) const { * code. * */ -Station::Station() : Station(0x0000, Options(), 0) { -} - -Station::Station(uint16_t _pi, const Options& options, int which_channel, bool has_pi) : - pi_(_pi), has_pi_(has_pi), options_(options), which_channel_(which_channel), tmc_(options) { +Station::Station(const Options& options, int which_channel) : + options_(options), which_channel_(which_channel), tmc_(options) { writer_builder_["indentation"] = ""; writer_builder_["precision"] = 7; writer_builder_.settings_["emitUTF8"] = true; @@ -227,7 +224,12 @@ Station::Station(uint16_t _pi, const Options& options, int which_channel, bool h std::unique_ptr(writer_builder_.newStreamWriter()); } -void Station::updateAndPrint(const Group& group, std::ostream* stream) { +Station::Station(const Options& options, int which_channel, uint16_t _pi) : Station(options, which_channel) { + pi_ = _pi; + has_pi_ = true; +} + +void Station::updateAndPrint(const Group& group, std::ostream& stream) { if (!has_pi_) return; @@ -265,7 +267,7 @@ void Station::updateAndPrint(const Group& group, std::ostream* stream) { if (options_.show_raw) { std::stringstream ss; - group.printHex(&ss); + group.printHex(ss); json_["raw_data"] = ss.str(); } @@ -337,7 +339,7 @@ void Station::updateAndPrint(const Group& group, std::ostream* stream) { writer_->write(json_, &ss); ss << '\n'; - *stream << ss.str() << std::flush; + stream << ss.str() << std::flush; } uint16_t Station::getPI() const { diff --git a/src/groups.h b/src/groups.h index c41ee06..dc9fe3c 100644 --- a/src/groups.h +++ b/src/groups.h @@ -72,29 +72,29 @@ bool operator<(const GroupType& type1, const GroupType& type2); class ProgramServiceName { public: - ProgramServiceName() : text(8) {} + ProgramServiceName() = default; void update(size_t pos, uint8_t byte1, uint8_t byte2) { text.set(pos, byte1, byte2); } - RDSString text; + RDSString text{8}; }; class LongPS { public: - LongPS() : text(32) { + LongPS() { text.setEncoding(RDSString::Encoding::UTF8); } void update(size_t pos, uint8_t byte1, uint8_t byte2) { text.set(pos, byte1, byte2); } - RDSString text; + RDSString text{32}; }; class RadioText { public: - RadioText() : text(64) {} + RadioText() = default; bool isABChanged(int new_ab) { const bool is = (ab != new_ab); ab = new_ab; @@ -119,10 +119,10 @@ class RadioText { }; }; - RDSString text; + RDSString text{64}; Plus plus; std::string previous_potentially_complete_message; - int ab { 0 }; + int ab{}; }; class PTYName { @@ -174,7 +174,7 @@ class Group { bool hasBLER() const; bool hasTime() const; std::chrono::time_point getRxTime() const; - void printHex(std::ostream* stream) const; + void printHex(std::ostream& stream) const; void disableOffsets(); void setBlock(eBlockNumber block_num, Block block); @@ -195,9 +195,10 @@ class Group { class Station { public: - Station(); - Station(uint16_t _pi, const Options& options, int which_channel, bool has_pi=true); - void updateAndPrint(const Group& group, std::ostream* stream); + Station() = delete; + Station(const Options& options, int which_channel, uint16_t _pi); + Station(const Options& options, int which_channel); + void updateAndPrint(const Group& group, std::ostream& stream); uint16_t getPI() const; private: diff --git a/src/options.cc b/src/options.cc index 04e931a..e3897c1 100644 --- a/src/options.cc +++ b/src/options.cc @@ -36,6 +36,7 @@ Options getOptions(int argc, char** argv) { { "input-hex", no_argument, 0, 'h'}, { "input", 1, 0, 'i'}, { "loctable", 1, 0, 'l'}, + { "output", 1, 0, 'o'}, { "show-partial", no_argument, 0, 'p'}, { "samplerate", 1, 0, 'r'}, { "show-raw", no_argument, 0, 'R'}, @@ -49,7 +50,7 @@ Options getOptions(int argc, char** argv) { int option_index = 0; int option_char; - while ((option_char = getopt_long(argc, argv, "bc:eEf:hi:l:pr:Rt:uvx", + while ((option_char = getopt_long(argc, argv, "bc:eEf:hi:l:o:pr:Rt:uvx", long_options, &option_index)) >= 0) { switch (option_char) { case 'b': // For backwards compatibility @@ -92,7 +93,19 @@ Options getOptions(int argc, char** argv) { } break; } - case 'x': + case 'o': { + const std::string output_type(optarg); + if (output_type == "hex") { + options.output_type = OutputType::Hex; + } else if (output_type == "json") { + options.output_type = OutputType::JSON; + } else { + std::cerr << "error: unknown output format '" << output_type << "'" << std::endl; + options.exit_failure = true; + } + break; + } + case 'x': // For backwards compatibility options.output_type = OutputType::Hex; break; case 'p': diff --git a/src/redsea.cc b/src/redsea.cc index 4b826e6..19b04a0 100644 --- a/src/redsea.cc +++ b/src/redsea.cc @@ -34,9 +34,8 @@ void printUsage() { "\n" "By default, an MPX signal (raw mono S16LE PCM sampled at 171 kHz) is expected via stdin.\n" "\n" - "-c, --channels CHANS Number of channels in the raw input signal.\n" - " Channels are interleaved streams of samples that\n" - " are demodulated independently.\n" + "-c, --channels CHANS Number of channels in the raw input signal. Each\n" + " channel is demodulated independently.\n" "\n" "-e, --feed-through Echo the input signal to stdout and print\n" " decoded groups to stderr.\n" @@ -51,43 +50,42 @@ void printUsage() { " readable by libsndfile should work.\n" "\n" "-i, --input FORMAT Decode stdin as FORMAT (see the wiki for more info):\n" - " bits Unsynchronized ASCII bit stream (011010110...).\n" - " All characters but '0' and '1' are ignored.\n" - " hex RDS Spy hex format.\n" - " mpx Mono S16LE PCM-encoded MPX waveform (default).\n" - " tef Serial data from the TEF6686 tuner.\n" + " bits (-b) Unsynchronized ASCII bit stream (011010110...).\n" + " All characters but '0' and '1' are ignored.\n" + " hex (-h) RDS Spy hex format.\n" + " mpx Mono S16LE PCM-encoded MPX waveform (default).\n" + " tef Serial data from the TEF6686 tuner.\n" "\n" "-l, --loctable DIR Load TMC location table from a directory in TMC\n" " Exchange format. This option can be specified\n" " multiple times to load several location tables.\n" "\n" - "-p, --show-partial Under noisy conditions, redsea may not be able to\n" - " fully receive all information. Multi-group data\n" - " such as PS names, RadioText, and alternative\n" - " frequencies are especially vulnerable. This option\n" - " makes it display them even if not fully received,\n" - " as partial_{ps,radiotext,alt_frequencies}.\n" + "-o, --output FORMAT Print output groups as FORMAT:\n" + " hex (-x) RDS Spy hex format.\n" + " json Newline-delimited json (default).\n" "\n" - "-r, --samplerate RATE Set stdin sample frequency in Hz. Will resample\n" - " (slow) if this differs from 171000 Hz.\n" + "-p, --show-partial Show some multi-group data even before they've been\n" + " fully received (PS names, RadioText, alternative\n" + " frequencies). partial_ will be prepended to their\n" + " names. This is good for noisy conditions.\n" + "\n" + "-r, --samplerate RATE Set sample frequency of the raw MPX input signal in Hz.\n" + " Will resample (slow) if this differs from 171000 Hz.\n" "\n" "-R, --show-raw Show raw group data as hex in the JSON stream.\n" "\n" "-t, --timestamp FORMAT Add time of decoding to JSON groups; see\n" " man strftime for formatting options (or\n" - " try \"%c\"). Use \"%f\" to add hundredths of\n" - " seconds.\n" + " try \"%c\"). Use \"%f\" to add hundredths of seconds.\n" "\n" "-u, --rbds RBDS mode; use North American program type names\n" - " and \"back-calculate\" the station's call sign from\n" + " and \"back-calculate\" the station\'s call sign from\n" " its PI code. Note that this calculation gives an\n" " incorrect call sign for most stations that transmit\n" " TMC.\n" "\n" "-v, --version Print version string and exit.\n" - "\n" - "-x, --output-hex Output hex groups in the RDS Spy format,\n" - " suppressing JSON output.\n"; + "\n"; } void printVersion() { @@ -113,10 +111,12 @@ int processMPXInput(Options options) { if (mpx.hasError()) return EXIT_FAILURE; + auto& output_stream = options.feed_thru ? std::cerr : std::cout; + std::vector channels; std::vector> subcarriers; for (int i = 0; i < options.num_channels; i++) { - channels.emplace_back(options, i); + channels.emplace_back(options, i, output_stream); subcarriers.push_back(std::make_unique(options)); } @@ -134,14 +134,14 @@ int processMPXInput(Options options) { } } - for (size_t i = 0; i < size_t(options.num_channels); i++) + for (int i = 0; i < options.num_channels; i++) channels[i].flush(); return EXIT_SUCCESS; } int processASCIIBitsInput(const Options& options) { - Channel channel(options, 0); + Channel channel(options, 0, std::cout); AsciiBitReader ascii_reader(options); while (!ascii_reader.eof()) { @@ -154,7 +154,7 @@ int processASCIIBitsInput(const Options& options) { } int processHexInput(const Options& options) { - Channel channel(options, 0); + Channel channel(options, 0, std::cout); while (!std::cin.eof()) { channel.processGroup(readHexGroup(options)); @@ -164,7 +164,7 @@ int processHexInput(const Options& options) { } int processTEFInput(const Options& options) { - Channel channel(options, 0); + Channel channel(options, 0, std::cout); while (!std::cin.eof()) { channel.processGroup(readTEFGroup(options)); diff --git a/test/test.cc b/test/test.cc new file mode 100644 index 0000000..45c65af --- /dev/null +++ b/test/test.cc @@ -0,0 +1,142 @@ +#include +#include + +#include + +#include "../ext/json/json.h" + +#include "../src/block_sync.h" +#include "../src/channel.h" +#include "../src/common.h" +#include "../src/groups.h" +#include "../src/options.h" +#include "test_helpers.h" + +TEST_CASE("Decodes basic info") { + redsea::Options options; + + // YLE X3M (fi) 2016-09-15 + const auto json_lines{decodeGroups({ + 0x6204'0130'966B'594C, + 0x6204'0131'93CD'4520, + 0x6204'0132'E472'5833, + 0x6204'0137'966B'4D20 + }, options, 0x6204)}; + + REQUIRE(json_lines.size() == 4); + for (auto group : json_lines) { + REQUIRE(group["pi"].asString() == "0x6204"); + REQUIRE(group["tp"].asBool() == false); + } + REQUIRE(json_lines[0]["di"]["dynamic_pty"].asBool() == false); + REQUIRE(json_lines[1]["di"]["compressed"].asBool() == false); + REQUIRE(json_lines[2]["di"]["artificial_head"].asBool() == false); + REQUIRE(json_lines[3]["di"]["stereo"].asBool() == true); + REQUIRE(json_lines[0]["is_music"].asBool() == false); + REQUIRE(json_lines[0]["prog_type"].asString() == "Varied"); + + REQUIRE(json_lines[3]["ps"].asString() == "YLE X3M "); +} + +TEST_CASE("Decodes radiotext") { + redsea::Options options; + + // String length method A: Terminated using 0x0D + { + // JACK 96.9 (ca) 2019-05-05 + const auto json_lines{decodeGroups({ + 0xC954'24F0'4A41'434B, // "JACK" + 0xC954'24F1'2039'362E, // " 96." + 0xC954'24F2'390D'0000 // "9\r " + }, options, 0xC954)}; + + REQUIRE(json_lines.size() == 3); + REQUIRE(json_lines.back()["radiotext"].asString() == "JACK 96.9"); + } + + // String length method B: Padded to 64 characters + { + // Radio Grün-Weiß (at) 2021-07-18 + const auto json_lines{decodeGroups({ + 0xA959'2410'4641'4E43, // "FANC" + 0xA959'2411'5920'2D20, // "Y - " + 0xA959'2412'426F'6C65, // "Bole" + 0xA959'2413'726F'2020, // "ro " + 0xA959'2414'2020'2020, // " " + 0xA959'2415'2020'2020, // ... + 0xA959'2416'2020'2020, 0xA959'2417'2020'2020, + 0xA959'2418'2020'2020, 0xA959'2419'2020'2020, + 0xA959'241A'2020'2020, 0xA959'241B'2020'2020, + 0xA959'241C'2020'2020, 0xA959'241D'2020'2020, + 0xA959'241E'2020'2020, 0xA959'241F'2020'2020 + }, options, 0xA959)}; + + REQUIRE(json_lines.size() == 16); + REQUIRE(json_lines.back()["radiotext"].asString() == "FANCY - Bolero"); + } + + // String length method C: Random-length string with no terminator + { + // Antenne Kärnten (at) 2021-07-26 + const auto json_lines{decodeGroups({ + 0xA540'2540'526F'6262, // "Robb" // REPEAT 1 + 0xA540'2541'6965'2057, // "ie W" + 0xA540'2542'696C'6C69, // "illi" + 0xA540'2543'616D'7320, // "ams " + 0xA540'2544'2D20'4665, // "- Fe" + 0xA540'2545'656C'2020, // "el " + 0xA540'2540'526F'6262, // REPEAT 2 + 0xA540'2541'6965'2057, + 0xA540'2542'696C'6C69, + 0xA540'2543'616D'7320, + 0xA540'2544'2D20'4665, + 0xA540'2545'656C'2020, + 0xA540'2540'526F'6262, // REPEAT 3 starts - length confirmed + }, options, 0xA540)}; + + REQUIRE(json_lines.size() == 13); + REQUIRE(json_lines.back()["radiotext"].asString() == "Robbie Williams - Feel"); + } + + // Non-ascii character + { + // YLE Vega (fi) 2016-09-15 + const auto json_lines{decodeGroups({ + 0x6205'2440'5665'6761, // "Vega" + 0x6205'2441'204B'7691, // " kvä" + 0x6205'2442'6C6C'2020, // "ll " + 0x6205'2443'2020'2020, // " " + 0x6205'2444'2020'2020, // ... + 0x6205'2445'2020'2020, 0x6205'2446'2020'2020, + 0x6205'2447'2020'2020, 0x6205'2448'2020'2020, + 0x6205'2449'2020'2020, 0x6205'244A'2020'2020, + 0x6205'244B'2020'2020, 0x6205'244C'2020'2020, + 0x6205'244D'2020'2020, 0x6205'244E'2020'2020, + 0x6205'244F'2020'2020 + }, options, 0x6205)}; + + REQUIRE(json_lines.size() == 16); + REQUIRE(json_lines.back()["radiotext"].asString() == "Vega Kväll"); + } + +} + +TEST_CASE("Decodes alternative frequencies") { + redsea::Options options; + + // Method A + { + // YLE Yksi (fi) 2016-09-15 + const auto json_lines{decodeGroups({ + 0x6201'00F7'E704'5349, + 0x6201'00F0'2217'594C, + 0x6201'00F1'1139'4520, + 0x6201'00F2'0A14'594B + }, options, 0x6201)}; + + REQUIRE(json_lines.size() == 4); + REQUIRE(json_lines.back().isMember("alt_frequencies_a")); + REQUIRE(isSameList(json_lines.back()["alt_frequencies_a"], + std::vector{87900, 90900, 89800, 89200, 93200, 88500, 89500})); + } +} \ No newline at end of file diff --git a/test/test_helpers.h b/test/test_helpers.h new file mode 100644 index 0000000..d302f4d --- /dev/null +++ b/test/test_helpers.h @@ -0,0 +1,75 @@ +#ifndef TEST_HELPERS_H_ +#define TEST_HELPERS_H_ + +#include "../ext/json/json.h" + +#include "../src/block_sync.h" +#include "../src/channel.h" +#include "../src/common.h" +#include "../src/groups.h" +#include "../src/options.h" + +#include + +using HexData = std::vector; + +// Convert synchronized hex data into groups. Error correction is omitted and ignored. +inline std::vector makeGroups(const HexData& hexdata) { + std::vector groups; + groups.reserve(hexdata.size()); + + for (const auto& hexgroup : hexdata) { + redsea::Group group; + group.disableOffsets(); + for (auto nblock : {redsea::BLOCK1, redsea::BLOCK2, redsea::BLOCK3, redsea::BLOCK4}) { + redsea::Block block; + block.data = hexgroup >> (16 * (3 - static_cast(nblock))) & 0xFFFF; + block.is_received = true; + group.setBlock(nblock, block); + } + groups.push_back(group); + } + + return groups; +} + +// Use JsonCPP to convert its own output back into objects. +inline std::vector decodeGroups(const std::vector& data, + const redsea::Options& options, uint16_t pi) { + std::vector result; + + std::stringstream json_stream; + redsea::Channel channel(options, json_stream, pi); + for (const auto& group : data) { + json_stream.str(""); + json_stream.clear(); + channel.processGroup(group); + if (!json_stream.str().empty()) { + Json::Value jsonroot; + json_stream >> jsonroot; + result.push_back(jsonroot); + } + } + + return result; +} + +inline std::vector decodeGroups(const HexData& hexdata, const redsea::Options& options, + uint16_t pi) { + return decodeGroups(makeGroups(hexdata), options, pi); +} + +template +bool isSameList(Json::Value json, const std::vector& list) { + if (json.size() != list.size()) { + return false; + } + for (size_t i{}; i < list.size(); i++) { + if (json[static_cast(i)] != list[i]) { + return false; + } + } + return true; +} + +#endif // TEST_HELPERS_H_