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 useful 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
yaml::Value Convert(const json::Value& json, To<yaml::Value>);
}
USERVER_NAMESPACE_END
USERVER_NAMESPACE_BEGIN
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()) {
for (const auto& elem : json) {
yaml_vb.PushBack(elem.ConvertTo<yaml::Value>());
}
} else if (json.IsObject()) {
for (auto it = json.begin(); it != json.end(); ++it) {
yaml_vb[it.GetName()] = it->ConvertTo<yaml::Value>();
}
}
return yaml_vb.ExtractValue();
}
}
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;
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;
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
)
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: