summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--test/meson.build1
-rw-r--r--test/span.cpp181
2 files changed, 182 insertions, 0 deletions
diff --git a/test/meson.build b/test/meson.build
index daaa1aac..8ab58ac1 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -17,6 +17,7 @@ public_tests = [
['geometry', 'geometry.cpp'],
['list-cameras', 'list-cameras.cpp'],
['signal', 'signal.cpp'],
+ ['span', 'span.cpp'],
]
internal_tests = [
diff --git a/test/span.cpp b/test/span.cpp
new file mode 100644
index 00000000..69f0732e
--- /dev/null
+++ b/test/span.cpp
@@ -0,0 +1,181 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * span.cpp - Span tests
+ */
+
+/*
+ * Include first to ensure the header is self-contained, as there's no span.cpp
+ * in libcamera.
+ */
+#include <libcamera/span.h>
+
+#include <array>
+#include <iostream>
+#include <vector>
+
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+class SpanTest : public Test
+{
+protected:
+ int run()
+ {
+ int i[4]{ 1, 2, 3, 4 };
+ std::array<int, 4> a{ 1, 2, 3, 4 };
+ const std::array<int, 4> ca{ 1, 2, 3, 4 };
+ std::vector<int> v{ 1, 2, 3, 4 };
+ const std::vector<int> cv{ 1, 2, 3, 4 };
+
+ /*
+ * Compile-test construction and usage of spans with static
+ * extent. Commented-out tests are expected not to compile, or
+ * to generate undefined behaviour.
+ */
+
+ Span<int, 0>{};
+ /* Span<int, 4>{}; */
+
+ Span<int, 4>{ &i[0], 4 };
+ Span<int, 4>{ &i[0], &i[3] };
+
+ Span<int, 4>{ i };
+ /* Span<float, 4>{ i }; */
+ /* Span<int, 2>{ i }; */
+
+ Span<int, 4>{ a };
+ Span<const int, 4>{ a };
+ /* Span<float, 4>{ a }; */
+ /* Span<int, 2>{ a }; */
+
+ Span<const int, 4>{ ca };
+ /* Span<const int, 2>{ ca }; */
+ /* Span<const float, 4>{ ca }; */
+ /* Span<int, 4>{ ca }; */
+
+ Span<int, 4>{ v };
+ Span<const int, 4>{ v };
+ /* Span<float, 4>{ v }; */
+
+ Span<const int, 4>{ v };
+ /* Span<int, 4>{ v }; */
+ /* Span<const float, 4>{ v }; */
+
+ Span<int, 4> staticSpan{ i };
+ Span<int, 4>{ staticSpan };
+ Span<const int, 4>{ staticSpan };
+ /* Span<const int, 2>{ staticSpan }; */
+
+ staticSpan = Span<int, 4>{ v };
+
+ staticSpan.begin();
+ staticSpan.cbegin();
+ staticSpan.end();
+ staticSpan.cend();
+ staticSpan.rbegin();
+ staticSpan.crbegin();
+ staticSpan.rend();
+ staticSpan.crend();
+
+ staticSpan.front();
+ staticSpan.back();
+ staticSpan[0];
+ staticSpan.data();
+
+ staticSpan.size();
+ staticSpan.size_bytes();
+
+ staticSpan.empty();
+
+ staticSpan.first<2>();
+ staticSpan.first(2);
+ /* staticSpan.first<6>(); */
+ /* staticSpan.first(6); */
+ staticSpan.last<2>();
+ staticSpan.last(2);
+ /* staticSpan.last<6>(); */
+ /* staticSpan.last(6); */
+ staticSpan.subspan<1>();
+ staticSpan.subspan<1, 2>();
+ staticSpan.subspan(1);
+ staticSpan.subspan(1, 2);
+ /* staticSpan.subspan(2, 4); */
+
+ /*
+ * Compile-test construction and usage of spans with static
+ * extent. Commented-out tests are expected not to compile, or
+ * to generate undefined behaviour.
+ */
+
+ Span<int>{};
+
+ Span<int>{ &i[0], 4 };
+ Span<int>{ &i[0], &i[3] };
+
+ Span<int>{ i };
+ /* Span<float>{ i }; */
+
+ Span<int>{ a };
+ Span<const int>{ a };
+ /* Span<float>{ a }; */
+
+ Span<const int>{ ca };
+ /* Span<const float>{ca}; */
+ /* Span<int>{ca}; */
+
+ Span<int>{ v };
+ Span<const int>{ v };
+ /* Span<float>{ v }; */
+
+ Span<const int>{ v };
+ /* Span<int>{ v }; */
+ /* Span<const float>{ v }; */
+
+ Span<int> dynamicSpan{ i };
+ Span<int>{ dynamicSpan };
+ Span<const int>{ dynamicSpan };
+
+ dynamicSpan = Span<int>{ a };
+
+ dynamicSpan.begin();
+ dynamicSpan.cbegin();
+ dynamicSpan.end();
+ dynamicSpan.cend();
+ dynamicSpan.rbegin();
+ dynamicSpan.crbegin();
+ dynamicSpan.rend();
+ dynamicSpan.crend();
+
+ dynamicSpan.front();
+ dynamicSpan.back();
+ dynamicSpan[0];
+ dynamicSpan.data();
+
+ dynamicSpan.size();
+ dynamicSpan.size_bytes();
+
+ dynamicSpan.empty();
+
+ dynamicSpan.first<2>();
+ dynamicSpan.first(2);
+ /* dynamicSpan.first<6>(); */
+ /* dynamicSpan.first(6); */
+ dynamicSpan.last<2>();
+ dynamicSpan.last(2);
+ /* dynamicSpan.last<6>(); */
+ /* dynamicSpan.last(6); */
+ dynamicSpan.subspan<1>();
+ dynamicSpan.subspan<1, 2>();
+ dynamicSpan.subspan(1);
+ dynamicSpan.subspan(1, 2);
+ /* dynamicSpan.subspan(2, 4); */
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(SpanTest)
393' href='#n393'>393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2019, Google Inc.
 *
 * options.cpp - cam - Options parsing
 */

#include <assert.h>
#include <getopt.h>
#include <iomanip>
#include <iostream>
#include <string.h>

#include "options.h"

/* -----------------------------------------------------------------------------
 * Option
 */

const char *Option::typeName() const
{
	switch (type) {
	case OptionNone:
		return "none";

	case OptionInteger:
		return "integer";

	case OptionString:
		return "string";

	case OptionKeyValue:
		return "key=value";
	}

	return "unknown";
}

/* -----------------------------------------------------------------------------
 * OptionBase<T>
 */

template<typename T>
bool OptionsBase<T>::empty() const
{
	return values_.empty();
}

template<typename T>
bool OptionsBase<T>::valid() const
{
	return valid_;
}

template<typename T>
bool OptionsBase<T>::isSet(const T &opt) const
{
	return values_.find(opt) != values_.end();
}

template<typename T>
const OptionValue &OptionsBase<T>::operator[](const T &opt) const
{
	static const OptionValue empty;

	auto it = values_.find(opt);
	if (it != values_.end())
		return it->second;
	return empty;
}

template<typename T>
void OptionsBase<T>::invalidate()
{
	valid_ = false;
}

template<typename T>
bool OptionsBase<T>::parseValue(const T &opt, const Option &option,
				const char *arg)
{
	OptionValue value;

	switch (option.type) {
	case OptionNone:
		break;

	case OptionInteger:
		unsigned int integer;

		if (arg) {
			char *endptr;
			integer = strtoul(arg, &endptr, 0);
			if (*endptr != '\0')
				return false;
		} else {
			integer = 0;
		}

		value = OptionValue(integer);
		break;

	case OptionString:
		value = OptionValue(arg ? arg : "");
		break;

	case OptionKeyValue:
		KeyValueParser *kvParser = option.keyValueParser;
		KeyValueParser::Options keyValues = kvParser->parse(arg);
		if (!keyValues.valid())
			return false;

		value = OptionValue(keyValues);
		break;
	}

	if (option.isArray)
		values_[opt].addValue(value);
	else
		values_[opt] = value;

	return true;
}

template class OptionsBase<int>;
template class OptionsBase<std::string>;

/* -----------------------------------------------------------------------------
 * KeyValueParser
 */

bool KeyValueParser::addOption(const char *name, OptionType type,
			       const char *help, OptionArgument argument)
{
	if (!name)
		return false;
	if (!help || help[0] == '\0')
		return false;
	if (argument != ArgumentNone && type == OptionNone)
		return false;

	/* Reject duplicate options. */
	if (optionsMap_.find(name) != optionsMap_.end())
		return false;

	optionsMap_[name] = Option({ 0, type, name, argument, nullptr,
				     help, nullptr, false });
	return true;
}

KeyValueParser::Options KeyValueParser::parse(const char *arguments)
{
	Options options;

	for (const char *pair = arguments; *arguments != '\0'; pair = arguments) {
		const char *comma = strchrnul(arguments, ',');
		size_t len = comma - pair;

		/* Skip over the comma. */
		arguments = *comma == ',' ? comma + 1 : comma;

		/* Skip to the next pair if the pair is empty. */
		if (!len)
			continue;

		std::string key;
		std::string value;

		const char *separator = static_cast<const char *>(memchr(pair, '=', len));
		if (!separator) {
			key = std::string(pair, len);
			value = "";
		} else {
			key = std::string(pair, separator - pair);
			value = std::string(separator + 1, comma - separator - 1);
		}

		/* The key is mandatory, the value might be optional. */
		if (key.empty())
			continue;

		if (optionsMap_.find(key) == optionsMap_.end()) {
			std::cerr << "Invalid option " << key << std::endl;
			return options;
		}

		OptionArgument arg = optionsMap_[key].argument;
		if (value.empty() && arg == ArgumentRequired) {
			std::cerr << "Option " << key << " requires an argument"
				  << std::endl;
			return options;
		} else if (!value.empty() && arg == ArgumentNone) {
			std::cerr << "Option " << key << " takes no argument"
				  << std::endl;
			return options;
		}

		const Option &option = optionsMap_[key];
		if (!options.parseValue(key, option, value.c_str())) {
			std::cerr << "Failed to parse '" << value << "' as "
				  << option.typeName() << " for option " << key
				  << std::endl;
			return options;
		}
	}

	options.valid_ = true;
	return options;
}

void KeyValueParser::usage(int indent)
{
	unsigned int space = 0;

	for (auto const &iter : optionsMap_) {
		const Option &option = iter.second;
		unsigned int length = 14;
		if (option.argument != ArgumentNone)
			length += 1 + strlen(option.typeName());
		if (option.argument == ArgumentOptional)
			length += 2;

		if (length > space)
			space = length;
	}

	space = (space + 7) / 8 * 8;

	for (auto const &iter : optionsMap_) {
		const Option &option = iter.second;
		std::string argument = option.name;

		if (option.argument != ArgumentNone) {
			if (option.argument == ArgumentOptional)
				argument += "[=";
			else
				argument += "=";
			argument += option.typeName();
			if (option.argument == ArgumentOptional)
				argument += "]";
		}

		std::cerr << std::setw(indent) << std::right << " "
			  << std::setw(space) << std::left << argument;

		for (const char *help = option.help, *end = help; end;) {
			end = strchr(help, '\n');
			if (end) {
				std::cerr << std::string(help, end - help + 1);
				std::cerr << std::setw(indent + space) << " ";
				help = end + 1;
			} else {
				std::cerr << help << std::endl;
			}
		}
	}
}

/* -----------------------------------------------------------------------------
 * OptionValue
 */

OptionValue::OptionValue()
	: type_(ValueNone), integer_(0)
{
}

OptionValue::OptionValue(int value)
	: type_(ValueInteger), integer_(value)
{
}

OptionValue::OptionValue(const char *value)
	: type_(ValueString), integer_(0), string_(value)
{
}

OptionValue::OptionValue(const std::string &value)
	: type_(ValueString), integer_(0), string_(value)
{
}

OptionValue::OptionValue(const KeyValueParser::Options &value)
	: type_(ValueKeyValue), integer_(0), keyValues_(value)
{
}

void OptionValue::addValue(const OptionValue &value)
{
	assert(type_ == ValueNone || type_ == ValueArray);

	type_ = ValueArray;
	array_.push_back(value);
}

OptionValue::operator int() const
{
	return toInteger();
}

OptionValue::operator std::string() const
{
	return toString();
}

OptionValue::operator KeyValueParser::Options() const
{
	return toKeyValues();
}

OptionValue::operator std::vector<OptionValue>() const
{
	return toArray();
}

int OptionValue::toInteger() const
{
	if (type_ != ValueInteger)
		return 0;

	return integer_;
}

std::string OptionValue::toString() const
{
	if (type_ != ValueString)
		return std::string();

	return string_;
}

KeyValueParser::Options OptionValue::toKeyValues() const
{
	if (type_ != ValueKeyValue)
		return KeyValueParser::Options();

	return keyValues_;
}

std::vector<OptionValue> OptionValue::toArray() const
{
	if (type_ != ValueArray)
		return std::vector<OptionValue>{};

	return array_;
}

/* -----------------------------------------------------------------------------
 * OptionsParser
 */

bool OptionsParser::addOption(int opt, OptionType type, const char *help,
			      const char *name, OptionArgument argument,
			      const char *argumentName, bool array)
{
	/*
	 * Options must have at least a short or long name, and a text message.
	 * If an argument is accepted, it must be described by argumentName.
	 */
	if (!isalnum(opt) && !name)
		return false;
	if (!help || help[0] == '\0')
		return false;
	if (argument != ArgumentNone && !argumentName)
		return false;

	/* Reject duplicate options. */
	if (optionsMap_.find(opt) != optionsMap_.end())
		return false;

	options_.push_back(Option({ opt, type, name, argument, argumentName,
				    help, nullptr, array }));
	optionsMap_[opt] = &options_.back();
	return true;
}

bool OptionsParser::addOption(int opt, KeyValueParser *parser, const char *help,
			      const char *name, bool array)
{
	if (!addOption(opt, OptionKeyValue, help, name, ArgumentRequired,
		       "key=value[,key=value,...]", array))
		return false;

	options_.back().keyValueParser = parser;
	return true;
}

OptionsParser::Options OptionsParser::parse(int argc, char **argv)
{
	OptionsParser::Options options;

	/*
	 * Allocate short and long options arrays large enough to contain all
	 * options.
	 */
	char shortOptions[options_.size() * 3 + 2];
	struct option longOptions[options_.size() + 1];
	unsigned int ids = 0;
	unsigned int idl = 0;

	shortOptions[ids++] = ':';

	for (const Option &option : options_) {
		if (option.hasShortOption()) {
			shortOptions[ids++] = option.opt;
			if (option.argument != ArgumentNone)
				shortOptions[ids++] = ':';
			if (option.argument == ArgumentOptional)
				shortOptions[ids++] = ':';
		}

		if (option.hasLongOption()) {
			longOptions[idl].name = option.name;

			switch (option.argument) {
			case ArgumentNone:
				longOptions[idl].has_arg = no_argument;
				break;
			case ArgumentRequired:
				longOptions[idl].has_arg = required_argument;
				break;
			case ArgumentOptional:
				longOptions[idl].has_arg = optional_argument;
				break;
			}

			longOptions[idl].flag = 0;
			longOptions[idl].val = option.opt;
			idl++;
		}
	}

	shortOptions[ids] = '\0';
	memset(&longOptions[idl], 0, sizeof(longOptions[idl]));

	opterr = 0;

	while (true) {
		int c = getopt_long(argc, argv, shortOptions, longOptions, nullptr);

		if (c == -1)
			break;

		if (c == '?' || c == ':') {
			if (c == '?')
				std::cerr << "Invalid option ";
			else
				std::cerr << "Missing argument for option ";
			std::cerr << argv[optind - 1] << std::endl;

			usage();
			return options;
		}

		const Option &option = *optionsMap_[c];
		if (!options.parseValue(c, option, optarg)) {
			parseValueError(option);
			usage();
			return options;
		}
	}

	options.valid_ = true;
	return options;
}

void OptionsParser::usage()
{
	std::cerr << "Options:" << std::endl;

	unsigned int indent = 0;

	for (const Option &option : options_) {
		unsigned int length = 14;
		if (option.hasLongOption())
			length += 2 + strlen(option.name);
		if (option.argument != ArgumentNone)
			length += 1 + strlen(option.argumentName);
		if (option.argument == ArgumentOptional)
			length += 2;
		if (option.isArray)
			length += 4;

		if (length > indent)
			indent = length;
	}

	indent = (indent + 7) / 8 * 8;

	for (const Option &option : options_) {
		std::string argument;
		if (option.hasShortOption())
			argument = std::string("  -")
				 + static_cast<char>(option.opt);
		else
			argument = "    ";

		if (option.hasLongOption()) {
			if (option.hasShortOption())
				argument += ", ";
			else
				argument += "  ";
			argument += std::string("--") + option.name;
		}

		if (option.argument != ArgumentNone) {
			if (option.argument == ArgumentOptional)
				argument += "[=";
			else
				argument += " ";
			argument += option.argumentName;
			if (option.argument == ArgumentOptional)
				argument += "]";
		}

		if (option.isArray)
			argument += " ...";

		std::cerr << std::setw(indent) << std::left << argument;

		for (const char *help = option.help, *end = help; end; ) {
			end = strchr(help, '\n');
			if (end) {
				std::cerr << std::string(help, end - help + 1);