diff options
| -rw-r--r-- | src/pointerlist.cc | 123 | ||||
| -rw-r--r-- | src/pointerlist.h | 74 | ||||
| -rw-r--r-- | test/ctor.cc | 17 | ||||
| -rw-r--r-- | test/pointerlist_test.cc | 320 | 
4 files changed, 534 insertions, 0 deletions
| diff --git a/src/pointerlist.cc b/src/pointerlist.cc new file mode 100644 index 0000000..c0242f7 --- /dev/null +++ b/src/pointerlist.cc @@ -0,0 +1,123 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include "pointerlist.h" + +#include <cstring> + +PointerList::PointerList(int argc, const char* const argv[]) +{ +	for(int i = 0; i < argc; ++i) +	{ +		push_back(argv[i]); +	} +} + +std::pair<int, const char* const*> PointerList::get() +{ +	argptrs.clear(); +	for(const auto& arg : *this) +	{ +		argptrs.push_back(arg.data()); +	} +	argptrs.push_back(nullptr); + +	return {argptrs.size() - 1, // size not counting the nullptr at the end +	        argptrs.data()}; +} + +EnvMap::EnvMap(const char* const env[]) +{ +	if(env == nullptr) +	{ +		return; +	} + +	auto ptr = env; +	while(*ptr) +	{ +		insert(std::string_view(*ptr)); +		++ptr; +	} +} + +EnvMap::EnvMap(const char* env) +{ +	if(env == nullptr) +	{ +		return; +	} + +	auto ptr = env; +	while(*ptr) +	{ +		insert(ptr); +		ptr += strlen(ptr) + 1; +	} +} + +std::string EnvMap::operator[](const std::string& key) const +{ +	try +	{ +		return data.at(key); +	} +	catch(...) +	{ +		return {}; +	} +} + +void EnvMap::clear() +{ +	data.clear(); +} + +void EnvMap::insert(std::string_view key_value) +{ +	auto equals_sign = key_value.find('='); +	if(equals_sign == std::string::npos) +	{ +		insert({key_value, ""}); +		return; +	} +	std::string key{key_value.substr(0, equals_sign)}; +	std::string value{key_value.substr(equals_sign + 1)}; // skip '=' +	insert({key, value}); +} + +void EnvMap::insert(const std::pair<std::string_view, std::string_view>& item) +{ +	data[std::string(item.first)] = item.second; +} + +bool EnvMap::contains(std::string_view key) const +{ +	return data.contains(std::string{key}); +} + +std::size_t EnvMap::size() const +{ +	return data.size(); +} + +std::string EnvMap::stringify() const +{ +	std::string str; +	for(const auto& [key, value] : data) +	{ +		str += key + "=" + value + '\0'; +	} +	str += '\0'; +	return str; +} + +std::pair<int, const char* const*> EnvMap::get() +{ +	pointerlist.clear(); +	for(const auto& [key, value] : data) +	{ +		pointerlist.push_back(key + "=" + value + '\0'); +	} +	return pointerlist.get(); +} diff --git a/src/pointerlist.h b/src/pointerlist.h new file mode 100644 index 0000000..988bb26 --- /dev/null +++ b/src/pointerlist.h @@ -0,0 +1,74 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#pragma once + +#include <string> +#include <vector> +#include <deque> +#include <utility> +#include <map> + +//! Maintains an (owning) list of string args and converts them to argc/argv +//! compatible arguments on request. +//! The returned pointers are guaranteed to be valid as long as the PointerList +//! object lifetime is not exceeded. +class PointerList +	: public std::deque<std::string> +{ +public: +	PointerList() = default; +	PointerList(int argc, const char* const argv[]); + +	//! Returns argc/argv pair from the current list of args +	//! The argv entry after the last is a nullptr (not included in the argc) +	std::pair<int, const char* const*> get(); + +private: +	std::vector<const char*> argptrs; +}; + + +//! Maintains an owning map of strings representing the env. +class EnvMap +{ +public: +	EnvMap() = default; + +	//! Initialize from an array of pointers to key=value\0 strings terminated +	//! by \0 +	EnvMap(const char* const env[]); + +	//! Initialize from a string of the format +	//!  key1=val\0key2=val\0...keyN=val\0\0 +	EnvMap(const char* env); + +	std::string operator[](const std::string& key) const; + +	//! Clear all items in the map +	void clear(); + +	//! Insert string from format: key=value +	void insert(std::string_view key_value); + +	//! Regular map insert +	void insert(const std::pair<std::string_view, std::string_view>& item); + +	//! Checks if the container contains element with specific key +	bool contains(std::string_view key) const; + +	std::size_t size() const; + +	//! Return string with the following format: +	//!  key1=val\0key2=val\0...keyN=val\0\0 +	std::string stringify() const; + +	//! Returns the map as argc/argv pair where each pointer points to a string +	//! of the format key=value\0 and is terminated with a nullptr +	std::pair<int, const char* const*> get(); + +private: +	std::map<std::string, std::string> data{}; + +	PointerList pointerlist; +}; diff --git a/test/ctor.cc b/test/ctor.cc index d69b1cf..acb232f 100644 --- a/test/ctor.cc +++ b/test/ctor.cc @@ -12,6 +12,23 @@ ctor::build_configurations ctorTestConfigs(const ctor::settings& settings)  		{  			.type = ctor::target_type::unit_test,  			.system = ctor::output_system::build, +			.target = "pointerlist_test", +			.sources = { +				"pointerlist_test.cc", +				"testmain.cc", +				"../src/pointerlist.cc", +			}, +			.flags = { +				.cxxflags = { +					"-std=c++20", "-O3", "-Wall", "-Werror", +					"-I../src", "-Iuunit", +					"-DOUTPUT=\"pointerlist\"", +				}, +			}, +		}, +		{ +			.type = ctor::target_type::unit_test, +			.system = ctor::output_system::build,  			.target = "deps_test",  			.sources = {  				"deps_test.cc", diff --git a/test/pointerlist_test.cc b/test/pointerlist_test.cc new file mode 100644 index 0000000..4274473 --- /dev/null +++ b/test/pointerlist_test.cc @@ -0,0 +1,320 @@ +// -*- c++ -*- +// Distributed under the BSD 2-Clause License. +// See accompanying file LICENSE for details. +#include <uunit.h> + +#include <set> +#include <algorithm> + +#include "pointerlist.h" + +class PointerListTest +	: public uUnit +{ +public: +	PointerListTest() +	{ +		uTEST(PointerListTest::test_zom_pointerlist_push); +		uTEST(PointerListTest::test_zom_pointerlist_from_args); +		uTEST(PointerListTest::test_zom_envmap_insert); +		uTEST(PointerListTest::test_zom_envmap_from_env); +		uTEST(PointerListTest::test_exceptional_env); +	} + +	void test_zom_pointerlist_push() +	{ +		using namespace std::string_literals; + +		{ // Zero +			PointerList args; +			uASSERT_EQUAL(0u, args.size()); +			auto [argc, argv] = args.get(); +			uASSERT_EQUAL(0, argc); +			uASSERT(nullptr != argv); +			uASSERT_EQUAL(nullptr, argv[0]); +		} + +		{ // One +			PointerList args; +			args.push_back("hello"); +			uASSERT_EQUAL(1u, args.size()); +			auto [argc, argv] = args.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello"s, std::string(argv[0])); +		} + +		{ // Many +			PointerList args; +			args.push_back("hello"); +			args.push_back("dear"); +			args.push_back("world"); +			uASSERT_EQUAL(3u, args.size()); +			auto [argc, argv] = args.get(); +			uASSERT_EQUAL(3, argc); +			uASSERT_EQUAL("hello"s, std::string(argv[0])); +			uASSERT_EQUAL("dear"s, std::string(argv[1])); +			uASSERT_EQUAL("world"s, std::string(argv[2])); +		} +	} + +	void test_zom_pointerlist_from_args() +	{ +		using namespace std::string_literals; + +		{ // Zero +			PointerList args(0, nullptr); +			uASSERT_EQUAL(0u, args.size()); +			auto [argc, argv] = args.get(); +			uASSERT_EQUAL(0, argc); +			uASSERT(nullptr != argv); +			uASSERT_EQUAL(nullptr, argv[0]); +		} + +		{ // One +			int _argc{1}; +			const char* _argv[] = { "hello" }; +			PointerList args(_argc, _argv); +			uASSERT_EQUAL(1u, args.size()); +			auto [argc, argv] = args.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello"s, std::string(argv[0])); +		} + +		{ // Many +			int _argc{3}; +			const char* _argv[] = { "hello", "dear", "world" }; +			PointerList args(_argc, _argv); +			uASSERT_EQUAL(3u, args.size()); +			auto [argc, argv] = args.get(); +			uASSERT_EQUAL(3, argc); +			// order must be preserved +			uASSERT_EQUAL("hello"s, std::string(argv[0])); +			uASSERT_EQUAL("dear"s, std::string(argv[1])); +			uASSERT_EQUAL("world"s, std::string(argv[2])); +		} +	} + +	void test_zom_envmap_insert() +	{ +		using namespace std::string_literals; + +		{ // Zero +			EnvMap env; +			uASSERT_EQUAL(0u, env.size()); + +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(0, argc); +			uASSERT(nullptr != argv); +			uASSERT_EQUAL(nullptr, argv[0]); + +			auto str = env.stringify(); +			uASSERT_EQUAL(1u, str.size()); +			uASSERT_EQUAL('\0', str[0]); +		} + +		{ // One (key only) +			EnvMap env; +			env.insert("hello"); +			uASSERT_EQUAL(1u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello="s, std::string(argv[0])); +			uASSERT_EQUAL(""s, env["hello"]); +		} +		{ // One (with value) +			EnvMap env; +			env.insert("hello=world"); +			uASSERT_EQUAL(1u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello=world"s, std::string(argv[0])); +			uASSERT_EQUAL("world"s, env["hello"]); + +			uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); +		} + +		{ // Overwrite one +			EnvMap env; +			env.insert("hello=world"); +			uASSERT_EQUAL(1u, env.size()); +			uASSERT_EQUAL("world"s, env["hello"s]); + +			env.insert("hello=foo"); +			uASSERT_EQUAL(1u, env.size()); +			uASSERT_EQUAL("foo"s, env["hello"s]); +		} + +		{ // Many +			EnvMap env; +			env.insert("hello=world"); +			env.insert("world=leader"); +			env.insert("dear=boar"); +			uASSERT_EQUAL(3u, env.size()); + +			uASSERT_EQUAL("boar"s, env["dear"s]); +			uASSERT_EQUAL("world"s, env["hello"s]); +			uASSERT_EQUAL("leader"s, env["world"s]); + +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(3, argc); +			// store and sort to verify unordered +			std::vector<std::string> vals{argv[0], argv[1], argv[2]}; +			std::ranges::sort(vals); +			uASSERT_EQUAL("dear=boar"s, vals[0]); +			uASSERT_EQUAL("hello=world"s, vals[1]); +			uASSERT_EQUAL("world=leader"s, vals[2]); + +			// test all combinations since ordering is not a requirement +			// exactly one of the must be true (boolean XOR) +			auto str = env.stringify(); +			uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != +			            ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != +			           ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != +			          ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != +			         ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != +			        ("world=leader\0hello=world\0dear=boar\0\0"s == str)); +		} +	} + +	void test_zom_envmap_from_env() +	{ +		using namespace std::string_literals; + +		{ // Zero +			const char* penv = nullptr; +			EnvMap env(penv); +			uASSERT_EQUAL(0u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(0, argc); +			uASSERT(nullptr != argv); +			uASSERT_EQUAL(nullptr, argv[0]); +		} + +		{ // Zero +			const char** ppenv = nullptr; +			EnvMap env(ppenv); +			uASSERT_EQUAL(0u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(0, argc); +			uASSERT(nullptr != argv); +			uASSERT_EQUAL(nullptr, argv[0]); +		} + +		{ // One (key only) +			const char* ptr = "hello\0\0"; +			EnvMap env(ptr); +			uASSERT_EQUAL(1u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello="s, std::string(argv[0])); +			uASSERT_EQUAL(""s, env["hello"]); +		} + +		{ // One (key only) +			const char* ptr[] = {"hello", nullptr}; +			EnvMap env(ptr); +			uASSERT_EQUAL(1u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello="s, std::string(argv[0])); +			uASSERT_EQUAL(""s, env["hello"]); +		} + +		{ // One (with value) +			const char* ptr = "hello=world\0\0"; +			EnvMap env(ptr); +			uASSERT_EQUAL(1u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello=world"s, std::string(argv[0])); +			uASSERT_EQUAL("world"s, env["hello"]); + +			uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); +		} + +		{ // One (with value) +			const char* ptr[] = {"hello=world\0", nullptr}; +			EnvMap env(ptr); +			uASSERT_EQUAL(1u, env.size()); +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(1, argc); +			uASSERT_EQUAL("hello=world"s, std::string(argv[0])); +			uASSERT_EQUAL("world"s, env["hello"]); + +			uASSERT_EQUAL("hello=world\0\0"s, env.stringify()); +		} + +		{ // Many +			const char* ptr = "hello=world\0world=leader\0dear=boar\0\0"; +			EnvMap env(ptr); +			uASSERT_EQUAL(3u, env.size()); + +			uASSERT_EQUAL("boar"s, env["dear"s]); +			uASSERT_EQUAL("world"s, env["hello"s]); +			uASSERT_EQUAL("leader"s, env["world"s]); + +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(3, argc); +			// store and sort to verify unordered +			std::vector<std::string> vals{argv[0], argv[1], argv[2]}; +			std::ranges::sort(vals); +			uASSERT_EQUAL("dear=boar"s, vals[0]); +			uASSERT_EQUAL("hello=world"s, vals[1]); +			uASSERT_EQUAL("world=leader"s, vals[2]); + +			// test all combinations since ordering is not a requirement +			// exactly one of the must be true (boolean XOR) +			auto str = env.stringify(); +			uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != +			            ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != +			           ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != +			          ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != +			         ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != +			        ("world=leader\0hello=world\0dear=boar\0\0"s == str)); +		} + +		{ // Many +			const char* ptr[] = +				{"hello=world\0", "world=leader\0", "dear=boar\0", nullptr}; +			EnvMap env(ptr); +			uASSERT_EQUAL(3u, env.size()); + +			uASSERT_EQUAL("boar"s, env["dear"s]); +			uASSERT_EQUAL("world"s, env["hello"s]); +			uASSERT_EQUAL("leader"s, env["world"s]); + +			auto [argc, argv] = env.get(); +			uASSERT_EQUAL(3, argc); +			// store and sort to verify unordered +			std::vector<std::string> vals{argv[0], argv[1], argv[2]}; +			std::ranges::sort(vals); +			uASSERT_EQUAL("dear=boar"s, vals[0]); +			uASSERT_EQUAL("hello=world"s, vals[1]); +			uASSERT_EQUAL("world=leader"s, vals[2]); + +			// test all combinations since ordering is not a requirement +			// exactly one of the must be true (boolean XOR) +			auto str = env.stringify(); +			uASSERT(((((("dear=boar\0hello=world\0world=leader\0\0"s == str) != +			            ("dear=boar\0world=leader\0hello=world\0\0"s == str)) != +			           ("hello=world\0dear=boar\0world=leader\0\0"s == str)) != +			          ("hello=world\0world=leader\0dear=boar\0\0"s == str)) != +			         ("world=leader\0dear=boar\0hello=world\0\0"s == str)) != +			        ("world=leader\0hello=world\0dear=boar\0\0"s == str)); +		} +	} + +	void test_exceptional_env() +	{ +		using namespace std::string_literals; + +		{ // Zero +			EnvMap env; +			uASSERT_EQUAL(0u, env.size()); +			uASSERT_EQUAL(""s, env["foo"]); // lookup of non-existing key +		} +	} +}; + +// Registers the fixture into the 'registry' +static PointerListTest test; | 
