userver: Non-Coroutine Console Tool
Loading...
Searching...
No Matches
Non-Coroutine Console Tool

Before you start

Make sure that you can compile and run framework tests as described at Configure, Build and Install.

Step by step guide

The userver framework allows to use it's non-coroutine parts by using the userver-universal CMake target. It provides usefull utilities like utils::FastPimpl, utils::TrivialBiMap, JSON and YAML formats, utils::AnyMovable, cache::LruMap and many other utilities. See Universal for a list of available functions and classes.

Let's write a simple JSON to YAML converter with the help of userver-universal.

Code

The implementation is quite straightforward. Include the necessary C++ Standard library and userver headers:

Write the logic for converting each of the JSON types to YAML type:

USERVER_NAMESPACE_BEGIN
namespace formats::parse {
yaml::Value Convert(const json::Value& json, To<yaml::Value>);
} // namespace formats::parse
USERVER_NAMESPACE_END
USERVER_NAMESPACE_BEGIN
namespace formats::parse {
yaml::Value Convert(const json::Value& json, To<yaml::Value>) {
yaml::ValueBuilder yaml_vb;
if (json.IsBool()) {
yaml_vb = json.ConvertTo<bool>();
} else if (json.IsInt64()) {
yaml_vb = json.ConvertTo<int64_t>();
} else if (json.IsUInt64()) {
yaml_vb = json.ConvertTo<uint64_t>();
} else if (json.IsDouble()) {
yaml_vb = json.ConvertTo<double>();
} else if (json.IsString()) {
yaml_vb = json.ConvertTo<std::string>();
} else if (json.IsArray()) {
yaml_vb = {common::Type::kArray};
for (const auto& elem : json) {
yaml_vb.PushBack(elem.ConvertTo<yaml::Value>());
}
} else if (json.IsObject()) {
yaml_vb = {common::Type::kObject};
for (auto it = json.begin(); it != json.end(); ++it) {
yaml_vb[it.GetName()] = it->ConvertTo<yaml::Value>();
}
}
return yaml_vb.ExtractValue();
}
} // namespace formats::parse
USERVER_NAMESPACE_END

Finally, read data from std::cin, parse it as JSON, convert to YAML and output it as text:

#include <iostream>
#include <json2yaml.hpp>
int main() {
namespace formats = USERVER_NAMESPACE::formats;
auto json = formats::json::FromStream(std::cin);
formats::yaml::Serialize(json.ConvertTo<formats::yaml::Value>(), std::cout);
std::cout << std::endl;
}

Build and Run

To build the sample, execute the following build steps at the userver/samples/json2yaml (if userver is installed into the system) or from userver root directory:

mkdir build_release
cd build_release
cmake -DCMAKE_BUILD_TYPE=Release ..
make userver-samples-json2yaml

After that a tool is compiled and it could be used:

bash
$ samples/json2yaml/userver-samples-json2yaml
{"key": {"nested-key": [1, 2.0, "hello!", {"key-again": 42}]}}
^D
key:
nested-key:
- 1
- 2
- hello!
- key-again: 42

Testing

The code could be tested using any of the unit-testing frameworks, like Boost.Test, GTest, and so forth. Here's how to do it with GTest:

  • Write a test:
    #include <json2yaml.hpp>
    #include <gtest/gtest.h>
    TEST(Json2Yaml, Basic) {
    namespace formats = USERVER_NAMESPACE::formats;
    auto json = formats::json::FromString(R"({"key": 42})");
    auto yaml = json.ConvertTo<formats::yaml::Value>();
    EXPECT_EQ(yaml["key"].As<int>(), 42);
    }
  • Add its run to CMakeLists.txt:
    add_executable(${PROJECT_NAME}-unittest unittests/json2yaml_test.cpp)
    target_link_libraries(${PROJECT_NAME}-unittest
    ${PROJECT_NAME}-lib
    userver::universal-utest
    )
    add_google_tests(${PROJECT_NAME}-unittest)
  • Run the test via ctest -V

The above code tests the conversion logic, but not the final binary that may have issues in invoking the above logic. To test the final binary let's use Python with pytest:

  • Inform CMake about the test and that it should be started in a python venv with pytest installed. Pass the path to the CMake built binary to venv:
    userver_venv_setup(
    REQUIREMENTS ${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt
    PYTHON_OUTPUT_VAR json2yaml_venv
    )
    add_test(
    NAME "${PROJECT_NAME}-pytest"
    COMMAND ${json2yaml_venv} -m pytest
    "--test-binary=${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}"
    "-c /dev/null" # Do not search for pytest.ini
    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
    )
  • Add a fixture to conftest.py to get the path to the CMake built binary:
    import pytest
    def pytest_addoption(parser) -> None:
    group = parser.getgroup('userver')
    group.addoption('--test-binary', type=str, help='Path to build utility.')
    @pytest.fixture(scope='session')
    def path_to_json2yaml(pytestconfig):
    return pytestconfig.option.test_binary
  • Write the test:
    import subprocess
    _EXPECTED_OUTPUT = """key:
    nested-key:
    - 1
    - 2
    - hello!
    - key-again: 42
    """
    def test_basic(path_to_json2yaml):
    pipe = subprocess.Popen(
    [path_to_json2yaml],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    stdin=subprocess.PIPE,
    )
    stdout, stderr = pipe.communicate(
    input=b'{"key":{"nested-key": [1, 2.0, "hello!", {"key-again": 42}]}}',
    )
    assert stdout.decode('utf-8') == _EXPECTED_OUTPUT, stderr

Full sources

See the full example at: