diff options
author | Abseil Team <absl-team@google.com> | 2019-05-07T19·56-0700 |
---|---|---|
committer | Eric Fiselier <eric@efcs.ca> | 2019-05-07T20·32-0400 |
commit | aa468ad75539619b47979911297efbb629c52e44 (patch) | |
tree | 660bdd268ea4bb18d565b253cce6b6a6bbca0590 /absl/flags | |
parent | cd86d0d20ab167c33b23d3875db68d1d4bad3a3b (diff) |
Export of internal Abseil changes.
-- 78293ba4eb4981991ae7e6edd25eb9245fcd7515 by Andy Soffer <asoffer@google.com>: internal changes PiperOrigin-RevId: 247073879 -- 7bd97e3aad0c3012b89a39392a6ad3f254e9f3c3 by Derek Mauro <dmauro@google.com>: Release commandline flags PiperOrigin-RevId: 247065920 -- 2a991849fd7b140a43b073076d194b61533fd199 by Tom Manshreck <shreck@google.com>: Add documentation for built-in usage flags PiperOrigin-RevId: 247058863 -- 14157b0de45841706bbd631284191fd94c313db9 by Derek Mauro <dmauro@google.com>: Internal change PiperOrigin-RevId: 247058292 -- ed67e0a80468596e30540b367727a250fa415b68 by Abseil Team <absl-team@google.com>: Internal Change. PiperOrigin-RevId: 246828655 -- e1bc8c2aa3f90b3d56c55c5b7244e718c919265d by Abseil Team <absl-team@google.com>: Internal change PiperOrigin-RevId: 246822325 -- 907a68f287199f749cb9bdb48571b50bc34731e1 by Eric Fiselier <ericwf@google.com>: Accept vector<bool>::reference in StrCat and friends. Converting vector<bool>::reference to alphanum requires two user defined conversions, which isn't allowed. In order to accept this, we need a special constructor that is only enabled for the bool proxy types. PiperOrigin-RevId: 246626732 -- fe4295fa95cc65dee8c881ba12dd8f516e68c40d by Abseil Team <absl-team@google.com>: Clarify the proper way to define AbslHashValue, and fix confusing indentation. PiperOrigin-RevId: 246406528 -- d7174681a72d4a25c8fd2b4d9f515a0763eff87c by Gennadiy Rozental <rogeeff@google.com>: Internal change PiperOrigin-RevId: 246358214 -- a7fdc19683cf1a5885e9e1af52fcdb2db1eda53b by Abseil Team <absl-team@google.com>: Fix sample template instantiation. PiperOrigin-RevId: 246354617 -- 29a23c6835f872948d09b24d890385bf195bc995 by Abseil Team <absl-team@google.com>: Fix incorrectly copy-pasted static_assert in absl::optional::value_or The static_assert for a move-constructible type previously said the type must be copy-constructible. PiperOrigin-RevId: 246313827 -- 41884d5872d4ea2c67875b00144b8c8a5859a295 by Greg Falcon <gfalcon@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 245418790 -- 7f90a7f94cdd5e21232c749efe952a750b5c43a2 by Abseil Team <absl-team@google.com>: Internal change PiperOrigin-RevId: 245412658 -- 730a329cf047d54b46971fce1781edd857208c2a by Greg Falcon <gfalcon@google.com>: internal change PiperOrigin-RevId: 245293234 -- cf0216be6338200cbb18167d3f3b2e98e372be77 by Greg Falcon <gfalcon@google.com>: Internal change PiperOrigin-RevId: 245288164 GitOrigin-RevId: 78293ba4eb4981991ae7e6edd25eb9245fcd7515 Change-Id: I5ea9a852c36c722bae2d6be65fb7f72473d94ab6
Diffstat (limited to 'absl/flags')
35 files changed, 8397 insertions, 0 deletions
diff --git a/absl/flags/BUILD.bazel b/absl/flags/BUILD.bazel new file mode 100644 index 000000000000..2d868b0ab8bb --- /dev/null +++ b/absl/flags/BUILD.bazel @@ -0,0 +1,351 @@ +# +# Copyright 2019 The Abseil Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +load( + "//absl:copts/configure_copts.bzl", + "ABSL_DEFAULT_COPTS", + "ABSL_DEFAULT_LINKOPTS", + "ABSL_TEST_COPTS", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # Apache 2.0 + +cc_library( + name = "internal", + srcs = [ + "internal/program_name.cc", + ], + hdrs = [ + "internal/path_util.h", + "internal/program_name.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/flags:__pkg__", + ], + deps = [ + "//absl/strings", + "//absl/synchronization", + ], +) + +cc_library( + name = "config", + srcs = [ + "usage_config.cc", + ], + hdrs = [ + "config.h", + "usage_config.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":internal", + "//absl/base:core_headers", + "//absl/strings", + "//absl/synchronization", + ], +) + +cc_library( + name = "marshalling", + srcs = [ + "marshalling.cc", + ], + hdrs = [ + "marshalling.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:core_headers", + "//absl/strings", + "//absl/strings:str_format", + ], +) + +cc_library( + name = "handle", + srcs = [ + "internal/commandlineflag.cc", + ], + hdrs = [ + "internal/commandlineflag.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/flags:__pkg__", + ], + deps = [ + ":config", + ":marshalling", + "//absl/base", + "//absl/base:core_headers", + "//absl/strings", + "//absl/synchronization", + "//absl/types:optional", + ], +) + +cc_library( + name = "registry", + srcs = [ + "internal/registry.cc", + "internal/type_erased.cc", + ], + hdrs = [ + "internal/registry.h", + "internal/type_erased.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/flags:__pkg__", + ], + deps = [ + ":config", + ":handle", + "//absl/base", + "//absl/base:core_headers", + "//absl/base:dynamic_annotations", + "//absl/strings", + "//absl/synchronization", + ], +) + +cc_library( + name = "flag", + srcs = [ + "flag.cc", + ], + hdrs = [ + "declare.h", + "flag.h", + "internal/flag.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":config", + ":handle", + ":marshalling", + ":registry", + "//absl/base", + "//absl/base:core_headers", + "//absl/strings", + ], +) + +cc_library( + name = "usage", + srcs = [ + "internal/usage.cc", + ], + hdrs = [ + "internal/usage.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/flags:__pkg__", + ], + deps = [ + ":config", + ":flag", + ":handle", + ":internal", + "//absl/strings", + "//absl/synchronization", + ], +) + +cc_library( + name = "parse", + srcs = ["parse.cc"], + hdrs = [ + "internal/parse.h", + "parse.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":config", + ":flag", + ":handle", + ":internal", + ":registry", + ":usage", + "//absl/strings", + "//absl/synchronization", + ], +) + +############################################################################ +# Unit tests in alpahabetical order. + +cc_test( + name = "commandlineflag_test", + size = "small", + srcs = [ + "internal/commandlineflag_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":flag", + ":handle", + ":registry", + "//absl/memory", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "flag_test", + size = "small", + srcs = [ + "flag_test.cc", + "flag_test_defs.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":flag", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "marshalling_test", + size = "small", + srcs = [ + "marshalling_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":marshalling", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "path_util_test", + size = "small", + srcs = [ + "internal/path_util_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":internal", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "parse_test", + size = "small", + srcs = [ + "parse_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":flag", + ":parse", + "//absl/base", + "//absl/base:scoped_set_env", + "//absl/strings", + "//absl/types:span", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "program_name_test", + size = "small", + srcs = [ + "internal/program_name_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":internal", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "type_erased_test", + size = "small", + srcs = [ + "internal/type_erased_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":flag", + ":registry", + "//absl/memory", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "usage_config_test", + size = "small", + srcs = [ + "usage_config_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":config", + ":internal", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "usage_test", + size = "small", + srcs = [ + "internal/usage_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":config", + ":flag", + ":internal", + ":parse", + ":usage", + "//absl/memory", + "//absl/strings", + "@com_google_googletest//:gtest", + ], +) diff --git a/absl/flags/CMakeLists.txt b/absl/flags/CMakeLists.txt new file mode 100644 index 000000000000..284d6279b19a --- /dev/null +++ b/absl/flags/CMakeLists.txt @@ -0,0 +1,317 @@ +# +# Copyright 2019 The Abseil Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME + flags_internal + SRCS + "internal/program_name.cc" + HDRS + "internal/path_util.h" + "internal/program_name.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::strings + absl::synchronization + PUBLIC +) + +absl_cc_library( + NAME + flags_config + SRCS + "usage_config.cc" + HDRS + "config.h" + "usage_config.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::flags_internal + absl::core_headers + absl::strings + absl::synchronization +) + +absl_cc_library( + NAME + flags_marshalling + SRCS + "marshalling.cc" + HDRS + "marshalling.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::strings + absl::str_format +) + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME + flags_handle + SRCS + "internal/commandlineflag.cc" + HDRS + "internal/commandlineflag.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::flags_config + absl::flags_marshalling + absl::base + absl::core_headers + absl::strings + absl::synchronization + absl::optional +) + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME + flags_registry + SRCS + "internal/registry.cc" + "internal/type_erased.cc" + HDRS + "internal/registry.h" + "internal/type_erased.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::flags_config + absl::flags_handle + absl::base + absl::core_headers + absl::dynamic_annotations + absl::strings + absl::synchronization +) + +absl_cc_library( + NAME + flags + SRCS + "flag.cc" + HDRS + "declare.h" + "flag.h" + "internal/flag.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::flags_config + absl::flags_handle + absl::flags_marshalling + absl::flags_registry + absl::base + absl::core_headers + absl::strings +) + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME + flags_usage + SRCS + "internal/usage.cc" + HDRS + "internal/usage.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::flags_config + absl::flags + absl::flags_handle + absl::flags_internal + absl::strings + absl::synchronization +) + +absl_cc_library( + NAME + flags_parse + SRCS + "parse.cc" + HDRS + "internal/parse.h" + "parse.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::flags_config + absl::flags + absl::flags_handle + absl::flags_internal + absl::flags_registry + absl::flags_usage + absl::strings + absl::synchronization +) + +############################################################################ +# Unit tests in alpahabetical order. + +absl_cc_test( + NAME + flags_commandlineflag_test + SRCS + "internal/commandlineflag_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::flags + absl::flags_handle + absl::flags_registry + absl::memory + absl::strings + gtest_main +) + +absl_cc_test( + NAME + flags_flag_test + SRCS + "flag_test.cc" + "flag_test_defs.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::flags + absl::strings + gtest_main +) + +absl_cc_test( + NAME + flags_marshalling_test + SRCS + "marshalling_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::flags_marshalling + gtest_main +) + +absl_cc_test( + NAME + flags_parse_test + SRCS + "parse_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::flags + absl::base + absl::flags_parse + absl::scoped_set_env + absl::span + absl::strings + gmock_main +) + +absl_cc_test( + NAME + flags_path_util_test + SRCS + "internal/path_util_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::flags_internal + gtest_main +) + +absl_cc_test( + NAME + flags_program_name_test + SRCS + "internal/program_name_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::flags_internal + absl::strings + gtest_main +) + +absl_cc_test( + NAME + flags_type_erased_test + SRCS + "internal/type_erased_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::flags + absl::flags_registry + absl::memory + absl::strings + gtest_main +) + +absl_cc_test( + NAME + flags_usage_config_test + SRCS + "usage_config_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::flags_config + absl::flags_internal + absl::strings + gtest_main +) + +absl_cc_test( + NAME + flags_usage_test + SRCS + "internal/usage_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::flags_config + absl::flags + absl::flags_internal + absl::flags_parse + absl::flags_usage + absl::memory + absl::strings + gtest +) diff --git a/absl/flags/config.h b/absl/flags/config.h new file mode 100644 index 000000000000..a734af4688de --- /dev/null +++ b/absl/flags/config.h @@ -0,0 +1,54 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_FLAGS_CONFIG_H_ +#define ABSL_FLAGS_CONFIG_H_ + +// Determine if we should strip string literals from the Flag objects. +#if !defined(ABSL_FLAGS_STRIP_NAMES) + +// Non-mobile linux platforms don't strip string literals. +#if (defined(__linux__) || defined(__Fuchsia__)) && !defined(__ANDROID__) +#define ABSL_FLAGS_STRIP_NAMES 0 + +// So do Macs (not iOS or embedded Apple platforms). +#elif defined(__APPLE__) +#include <TargetConditionals.h> +#if !TARGET_OS_IPHONE && !TARGET_OS_EMBEDDED +#define ABSL_FLAGS_STRIP_NAMES 0 +#endif + +// And Windows. +#elif defined(_WIN32) +#define ABSL_FLAGS_STRIP_NAMES 0 + +// And Myriad. +#elif defined(__myriad2__) +#define ABSL_FLAGS_STRIP_NAMES 0 + +#endif +#endif // !defined(ABSL_FLAGS_STRIP_NAMES) + +#if ABSL_FLAGS_STRIP_NAMES +#if !defined(ABSL_FLAGS_STRIP_HELP) +#define ABSL_FLAGS_STRIP_HELP 1 +#endif +#else +#if !defined(ABSL_FLAGS_STRIP_HELP) +#define ABSL_FLAGS_STRIP_HELP 0 +#endif +#endif + +#endif // ABSL_FLAGS_CONFIG_H_ diff --git a/absl/flags/declare.h b/absl/flags/declare.h new file mode 100644 index 000000000000..556ec5e9aef9 --- /dev/null +++ b/absl/flags/declare.h @@ -0,0 +1,58 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: declare.h +// ----------------------------------------------------------------------------- +// +// This file defines the ABSL_DECLARE_FLAG macro, allowing you to declare an +// `absl::Flag` for use within a translation unit. You should place this +// declaration within the header file associated with the .cc file that defines +// and owns the `Flag`. + +#ifndef ABSL_FLAGS_DECLARE_H_ +#define ABSL_FLAGS_DECLARE_H_ + +#include "absl/strings/string_view.h" + +namespace absl { +namespace flags_internal { + +// absl::Flag<T> represents a flag of type 'T' created by ABSL_FLAG. +template <typename T> +class Flag; + +} // namespace flags_internal + +// Flag +// +// Forward declaration of the `absl::Flag` type for use in defining the macro. +template <typename T> +using Flag = flags_internal::Flag<T>; + +} // namespace absl + +// ABSL_DECLARE_FLAG() +// +// This macro is a convenience for declaring use of an `absl::Flag` within a +// translation unit. This macro should be used within a header file to +// declare usage of the flag within any .cc file including that header file. +// +// The ABSL_DECLARE_FLAG(type, name) macro expands to: +// +// extern absl::Flag<type> FLAGS_name; +#define ABSL_DECLARE_FLAG(type, name) extern ::absl::Flag<type> FLAGS_##name + +#endif // ABSL_FLAGS_DECLARE_H_ diff --git a/absl/flags/flag.cc b/absl/flags/flag.cc new file mode 100644 index 000000000000..ba002edd24f1 --- /dev/null +++ b/absl/flags/flag.cc @@ -0,0 +1,52 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/flag.h" + +#include <cstring> + +namespace absl { + +// We want to validate the type mismatch between type definition and +// declaration. The lock-free implementation does not allow us to do it, +// so in debug builds we always use the slower implementation, which always +// validates the type. +#ifndef NDEBUG +#define ABSL_FLAGS_ATOMIC_GET(T) \ + T GetFlag(const absl::Flag<T>& flag) { \ + T result; \ + flag.internal.Read(&result, &flags_internal::FlagOps<T>); \ + return result; \ + } +#else +#define ABSL_FLAGS_ATOMIC_GET(T) \ + T GetFlag(const absl::Flag<T>& flag) { \ + const int64_t r = flag.internal.atomic.load(std::memory_order_acquire); \ + if (r != flags_internal::CommandLineFlag::kAtomicInit) { \ + T t; \ + memcpy(&t, &r, sizeof(T)); \ + return t; \ + } \ + T result; \ + flag.internal.Read(&result, &flags_internal::FlagOps<T>); \ + return result; \ + } +#endif + +ABSL_FLAGS_INTERNAL_FOR_EACH_LOCK_FREE(ABSL_FLAGS_ATOMIC_GET) + +#undef ABSL_FLAGS_ATOMIC_GET + +} // namespace absl diff --git a/absl/flags/flag.h b/absl/flags/flag.h new file mode 100644 index 000000000000..2cbbb45ceacb --- /dev/null +++ b/absl/flags/flag.h @@ -0,0 +1,281 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: flag.h +// ----------------------------------------------------------------------------- +// +// This header file defines the `absl::Flag<T>` type for holding command-line +// flag data, and abstractions to create, get and set such flag data. +// +// It is important to note that this type is **unspecified** (an implementation +// detail) and you do not construct or manipulate actual `absl::Flag<T>` +// instances. Instead, you define and declare flags using the +// `ABSL_FLAG()` and `ABSL_DECLARE_FLAG()` macros, and get and set flag values +// using the `absl::GetFlag()` and `absl::SetFlag()` functions. + +#ifndef ABSL_FLAGS_FLAG_H_ +#define ABSL_FLAGS_FLAG_H_ + +#include "absl/base/attributes.h" +#include "absl/base/casts.h" +#include "absl/flags/config.h" +#include "absl/flags/declare.h" +#include "absl/flags/internal/commandlineflag.h" +#include "absl/flags/internal/flag.h" +#include "absl/flags/marshalling.h" + +namespace absl { + +// Flag +// +// An `absl::Flag` holds a command-line flag value, providing a runtime +// parameter to a binary. Such flags should be defined in the global namespace +// and (preferably) in the module containing the binary's `main()` function. +// +// You should not construct and cannot use the `absl::Flag` type directly; +// instead, you should declare flags using the `ABSL_DECLARE_FLAG()` macro +// within a header file, and define your flag using `ABSL_FLAG()` within your +// header's associated `.cc` file. Such flags will be named `FLAGS_name`. +// +// Example: +// +// .h file +// +// // Declares usage of a flag named "FLAGS_count" +// ABSL_DECLARE_FLAG(int, count); +// +// .cc file +// +// // Defines a flag named "FLAGS_count" with a default `int` value of 0. +// ABSL_FLAG(int, count, 0, "Count of items to process"); +// +// No public methods of `absl::Flag<T>` are part of the Abseil Flags API. +template <typename T> +using Flag = flags_internal::Flag<T>; + +// GetFlag() +// +// Returns the value (of type `T`) of an `absl::Flag<T>` instance, by value. Do +// not construct an `absl::Flag<T>` directly and call `absl::GetFlag()`; +// instead, refer to flag's constructed variable name (e.g. `FLAGS_name`). +// Because this function returns by value and not by reference, it is +// thread-safe, but note that the operation may be expensive; as a result, avoid +// `absl::GetFlag()` within any tight loops. +// +// Example: +// +// // FLAGS_count is a Flag of type `int` +// int my_count = absl::GetFlag(FLAGS_count); +// +// // FLAGS_firstname is a Flag of type `std::string` +// std::string first_name = absl::GetFlag(FLAGS_firstname); +template <typename T> +T GetFlag(const absl::Flag<T>& flag) { +#define ABSL_FLAGS_INTERNAL_LOCK_FREE_VALIDATE(BIT) \ + static_assert( \ + !std::is_same<T, BIT>::value, \ + "Do not specify explicit template parameters to absl::GetFlag"); + ABSL_FLAGS_INTERNAL_FOR_EACH_LOCK_FREE(ABSL_FLAGS_INTERNAL_LOCK_FREE_VALIDATE) +#undef ABSL_FLAGS_INTERNAL_LOCK_FREE_VALIDATE + + // Implementation notes: + // + // We are wrapping a union around the value of `T` to serve three purposes: + // + // 1. `U.value` has correct size and alignment for a value of type `T` + // 2. The `U.value` constructor is not invoked since U's constructor does not + // do it explicitly. + // 3. The `U.value` destructor is invoked since U's destructor does it + // explicitly. This makes `U` a kind of RAII wrapper around non default + // constructible value of T, which is destructed when we leave the scope. + // We do need to destroy U.value, which is constructed by + // CommandLineFlag::Read even though we left it in a moved-from state + // after std::move. + // + // All of this serves to avoid requiring `T` being default constructible. + union U { + T value; + U() {} + ~U() { value.~T(); } + }; + U u; + + flag.internal.Read(&u.value, &flags_internal::FlagOps<T>); + return std::move(u.value); +} + +// Overload for `GetFlag()` for types that support lock-free reads. +#define ABSL_FLAGS_INTERNAL_LOCK_FREE_EXPORT(T) \ + extern T GetFlag(const absl::Flag<T>& flag); +ABSL_FLAGS_INTERNAL_FOR_EACH_LOCK_FREE(ABSL_FLAGS_INTERNAL_LOCK_FREE_EXPORT) +#undef ABSL_FLAGS_INTERNAL_LOCK_FREE_EXPORT + +// SetFlag() +// +// Sets the value of an `absl::Flag` to the value `v`. Do not construct an +// `absl::Flag<T>` directly and call `absl::SetFlag()`; instead, use the +// flag's variable name (e.g. `FLAGS_name`). This function is +// thread-safe, but is potentially expensive. Avoid setting flags in general, +// but especially within performance-critical code. +template <typename T> +void SetFlag(absl::Flag<T>* flag, const T& v) { + flag->internal.Write(&v, &flags_internal::FlagOps<T>); +} + +// Overload of `SetFlag()` to allow callers to pass in a value that is +// convertible to `T`. E.g., use this overload to pass a "const char*" when `T` +// is `std::string`. +template <typename T, typename V> +void SetFlag(absl::Flag<T>* flag, const V& v) { + T value(v); + SetFlag<T>(flag, value); +} + +} // namespace absl + + +// ABSL_FLAG() +// +// This macro defines an `absl::Flag<T>` instance of a specified type `T`: +// +// ABSL_FLAG(T, name, default_value, help); +// +// where: +// +// * `T` is a supported flag type (See below), +// * `name` designates the name of the flag (as a global variable +// `FLAGS_name`), +// * `default_value` is an expression holding the default value for this flag +// (which must be implicitly convertible to `T`), +// * `help` is the help text, which can also be an expression. +// +// This macro expands to a flag named 'FLAGS_name' of type 'T': +// +// absl::Flag<T> FLAGS_name = ...; +// +// Note that all such instances are created as global variables. +// +// For `ABSL_FLAG()` values that you wish to expose to other translation units, +// it is recommended to define those flags within the `.cc` file associated with +// the header where the flag is declared. +// +// Note: do not construct objects of type `absl::Flag<T>` directly. Only use the +// `ABSL_FLAG()` macro for such construction. +#define ABSL_FLAG(Type, name, default_value, help) \ + ABSL_FLAG_IMPL(Type, name, default_value, help) + +// ABSL_FLAG().OnUpdate() +// +// Defines a flag of type `T` with a callback attached: +// +// ABSL_FLAG(T, name, default_value, help).OnUpdate(callback); +// +// After any setting of the flag value, the callback will be called at least +// once. A rapid sequence of changes may be merged together into the same +// callback. No concurrent calls to the callback will be made for the same +// flag. Callbacks are allowed to read the current value of the flag but must +// not mutate that flag. +// +// The update mechanism guarantees "eventual consistency"; if the callback +// derives an auxiliary data structure from the flag value, it is guaranteed +// that eventually the flag value and the derived data structure will be +// consistent. +// +// Note: ABSL_FLAG.OnUpdate() does not have a public definition. Hence, this +// comment serves as its API documentation. + + +// ----------------------------------------------------------------------------- +// Implementation details below this section +// ----------------------------------------------------------------------------- + +// ABSL_FLAG_IMPL macro definition conditional on ABSL_FLAGS_STRIP_NAMES + +#if ABSL_FLAGS_STRIP_NAMES +#define ABSL_FLAG_IMPL_FLAGNAME(txt) "" +#define ABSL_FLAG_IMPL_FILENAME() "" +#define ABSL_FLAG_IMPL_REGISTRAR(T, flag) \ + absl::flags_internal::FlagRegistrar<T, false>(&flag) +#else +#define ABSL_FLAG_IMPL_FLAGNAME(txt) txt +#define ABSL_FLAG_IMPL_FILENAME() __FILE__ +#define ABSL_FLAG_IMPL_REGISTRAR(T, flag) \ + absl::flags_internal::FlagRegistrar<T, true>(&flag) +#endif + +// ABSL_FLAG_IMPL macro definition conditional on ABSL_FLAGS_STRIP_HELP + +#if ABSL_FLAGS_STRIP_HELP +#define ABSL_FLAG_IMPL_FLAGHELP(txt) absl::flags_internal::kStrippedFlagHelp +#else +#define ABSL_FLAG_IMPL_FLAGHELP(txt) txt +#endif + +#define ABSL_FLAG_IMPL_DECLARE_HELP_WRAPPER(name, txt) \ + static std::string AbslFlagsWrapHelp##name() { \ + return ABSL_FLAG_IMPL_FLAGHELP(txt); \ + } + +#define ABSL_FLAG_IMPL_DECLARE_DEF_VAL_WRAPPER(name, Type, default_value) \ + static void* AbslFlagsInitFlag##name() { \ + return absl::flags_internal::MakeFromDefaultValue<Type>(default_value); \ + } + +// ABSL_FLAG_IMPL +// +// Note: Name of registrar object is not arbitrary. It is used to "grab" +// global name for FLAGS_no<flag_name> symbol, thus preventing the possibility +// of defining two flags with names foo and nofoo. +#define ABSL_FLAG_IMPL(Type, name, default_value, help) \ + namespace absl {} \ + ABSL_FLAG_IMPL_DECLARE_DEF_VAL_WRAPPER(name, Type, default_value) \ + ABSL_FLAG_IMPL_DECLARE_HELP_WRAPPER(name, help) \ + absl::Flag<Type> FLAGS_##name( \ + ABSL_FLAG_IMPL_FLAGNAME(#name), \ + &AbslFlagsWrapHelp##name, \ + ABSL_FLAG_IMPL_FILENAME(), \ + &absl::flags_internal::FlagMarshallingOps<Type>, \ + &AbslFlagsInitFlag##name); \ + extern bool FLAGS_no##name; \ + bool FLAGS_no##name = ABSL_FLAG_IMPL_REGISTRAR(Type, FLAGS_##name) + +// ABSL_RETIRED_FLAG +// +// Designates the flag (which is usually pre-existing) as "retired." A retired +// flag is a flag that is now unused by the program, but may still be passed on +// the command line, usually by production scripts. A retired flag is ignored +// and code can't access it at runtime. +// +// This macro registers a retired flag with given name and type, with a name +// identical to the name of the original flag you are retiring. The retired +// flag's type can change over time, so that you can retire code to support a +// custom flag type. +// +// This macro has the same signature as `ABSL_FLAG`. To retire a flag, simply +// replace an `ABSL_FLAG` definition with `ABSL_RETIRED_FLAG`, leaving the +// arguments unchanged (unless of course you actually want to retire the flag +// type at this time as well). +// +// `default_value` is only used as a double check on the type. `explanation` is +// unused. +// TODO(rogeeff): Return an anonymous struct instead of bool, and place it into +// the unnamed namespace. +#define ABSL_RETIRED_FLAG(type, flagname, default_value, explanation) \ + ABSL_ATTRIBUTE_UNUSED static const bool ignored_##flagname = \ + ([] { return type(default_value); }, \ + absl::flags_internal::RetiredFlag<type>(#flagname)) + +#endif // ABSL_FLAGS_FLAG_H_ diff --git a/absl/flags/flag_test.cc b/absl/flags/flag_test.cc new file mode 100644 index 000000000000..1bcd7e96868e --- /dev/null +++ b/absl/flags/flag_test.cc @@ -0,0 +1,482 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/flag.h" + +#include "gtest/gtest.h" +#include "absl/strings/match.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" + +ABSL_DECLARE_FLAG(int64_t, mistyped_int_flag); +ABSL_DECLARE_FLAG(std::vector<std::string>, mistyped_string_flag); + +namespace { + +namespace flags = absl::flags_internal; + +std::string TestHelpMsg() { return "help"; } +template <typename T> +void* TestMakeDflt() { + return new T{}; +} +void TestCallback() {} + +template <typename T> +bool TestConstructionFor() { + constexpr flags::Flag<T> f1("f1", &TestHelpMsg, "file", + &absl::flags_internal::FlagMarshallingOps<T>, + &TestMakeDflt<T>); + EXPECT_EQ(f1.Name(), "f1"); + EXPECT_EQ(f1.Help(), "help"); + EXPECT_EQ(f1.Filename(), "file"); + + ABSL_CONST_INIT static flags::Flag<T> f2( + "f2", &TestHelpMsg, "file", &absl::flags_internal::FlagMarshallingOps<T>, + &TestMakeDflt<T>); + flags::FlagRegistrar<T, false>(&f2).OnUpdate(TestCallback); + + EXPECT_EQ(f2.Name(), "f2"); + EXPECT_EQ(f2.Help(), "help"); + EXPECT_EQ(f2.Filename(), "file"); + + return true; +} + +struct UDT { + UDT() = default; + UDT(const UDT&) = default; +}; +bool AbslParseFlag(absl::string_view, UDT*, std::string*) { return true; } +std::string AbslUnparseFlag(const UDT&) { return ""; } + +TEST(FlagTest, TestConstruction) { + TestConstructionFor<bool>(); + TestConstructionFor<int16_t>(); + TestConstructionFor<uint16_t>(); + TestConstructionFor<int32_t>(); + TestConstructionFor<uint32_t>(); + TestConstructionFor<int64_t>(); + TestConstructionFor<uint64_t>(); + TestConstructionFor<double>(); + TestConstructionFor<float>(); + TestConstructionFor<std::string>(); + + TestConstructionFor<UDT>(); +} + +// -------------------------------------------------------------------- + +} // namespace + +ABSL_DECLARE_FLAG(bool, test_flag_01); +ABSL_DECLARE_FLAG(int, test_flag_02); +ABSL_DECLARE_FLAG(int16_t, test_flag_03); +ABSL_DECLARE_FLAG(uint16_t, test_flag_04); +ABSL_DECLARE_FLAG(int32_t, test_flag_05); +ABSL_DECLARE_FLAG(uint32_t, test_flag_06); +ABSL_DECLARE_FLAG(int64_t, test_flag_07); +ABSL_DECLARE_FLAG(uint64_t, test_flag_08); +ABSL_DECLARE_FLAG(double, test_flag_09); +ABSL_DECLARE_FLAG(float, test_flag_10); +ABSL_DECLARE_FLAG(std::string, test_flag_11); + +namespace { + +#if !ABSL_FLAGS_STRIP_NAMES + +TEST(FlagTest, TestFlagDeclaration) { + // test that we can access flag objects. + EXPECT_EQ(FLAGS_test_flag_01.Name(), "test_flag_01"); + EXPECT_EQ(FLAGS_test_flag_02.Name(), "test_flag_02"); + EXPECT_EQ(FLAGS_test_flag_03.Name(), "test_flag_03"); + EXPECT_EQ(FLAGS_test_flag_04.Name(), "test_flag_04"); + EXPECT_EQ(FLAGS_test_flag_05.Name(), "test_flag_05"); + EXPECT_EQ(FLAGS_test_flag_06.Name(), "test_flag_06"); + EXPECT_EQ(FLAGS_test_flag_07.Name(), "test_flag_07"); + EXPECT_EQ(FLAGS_test_flag_08.Name(), "test_flag_08"); + EXPECT_EQ(FLAGS_test_flag_09.Name(), "test_flag_09"); + EXPECT_EQ(FLAGS_test_flag_10.Name(), "test_flag_10"); + EXPECT_EQ(FLAGS_test_flag_11.Name(), "test_flag_11"); +} +#endif // !ABSL_FLAGS_STRIP_NAMES + +// -------------------------------------------------------------------- + +} // namespace + +ABSL_FLAG(bool, test_flag_01, true, "test flag 01"); +ABSL_FLAG(int, test_flag_02, 1234, "test flag 02"); +ABSL_FLAG(int16_t, test_flag_03, -34, "test flag 03"); +ABSL_FLAG(uint16_t, test_flag_04, 189, "test flag 04"); +ABSL_FLAG(int32_t, test_flag_05, 10765, "test flag 05"); +ABSL_FLAG(uint32_t, test_flag_06, 40000, "test flag 06"); +ABSL_FLAG(int64_t, test_flag_07, -1234567, "test flag 07"); +ABSL_FLAG(uint64_t, test_flag_08, 9876543, "test flag 08"); +ABSL_FLAG(double, test_flag_09, -9.876e-50, "test flag 09"); +ABSL_FLAG(float, test_flag_10, 1.234e12f, "test flag 10"); +ABSL_FLAG(std::string, test_flag_11, "", "test flag 11"); + +namespace { + +#if !ABSL_FLAGS_STRIP_NAMES +TEST(FlagTest, TestFlagDefinition) { + absl::string_view expected_file_name = "absl/flags/flag_test.cc"; + + EXPECT_EQ(FLAGS_test_flag_01.Name(), "test_flag_01"); + EXPECT_EQ(FLAGS_test_flag_01.Help(), "test flag 01"); + EXPECT_TRUE( + absl::EndsWith(FLAGS_test_flag_01.Filename(), expected_file_name)); + + EXPECT_EQ(FLAGS_test_flag_02.Name(), "test_flag_02"); + EXPECT_EQ(FLAGS_test_flag_02.Help(), "test flag 02"); + EXPECT_TRUE( + absl::EndsWith(FLAGS_test_flag_02.Filename(), expected_file_name)); + + EXPECT_EQ(FLAGS_test_flag_03.Name(), "test_flag_03"); + EXPECT_EQ(FLAGS_test_flag_03.Help(), "test flag 03"); + EXPECT_TRUE( + absl::EndsWith(FLAGS_test_flag_03.Filename(), expected_file_name)); + + EXPECT_EQ(FLAGS_test_flag_04.Name(), "test_flag_04"); + EXPECT_EQ(FLAGS_test_flag_04.Help(), "test flag 04"); + EXPECT_TRUE( + absl::EndsWith(FLAGS_test_flag_04.Filename(), expected_file_name)); + + EXPECT_EQ(FLAGS_test_flag_05.Name(), "test_flag_05"); + EXPECT_EQ(FLAGS_test_flag_05.Help(), "test flag 05"); + EXPECT_TRUE( + absl::EndsWith(FLAGS_test_flag_05.Filename(), expected_file_name)); + + EXPECT_EQ(FLAGS_test_flag_06.Name(), "test_flag_06"); + EXPECT_EQ(FLAGS_test_flag_06.Help(), "test flag 06"); + EXPECT_TRUE( + absl::EndsWith(FLAGS_test_flag_06.Filename(), expected_file_name)); + + EXPECT_EQ(FLAGS_test_flag_07.Name(), "test_flag_07"); + EXPECT_EQ(FLAGS_test_flag_07.Help(), "test flag 07"); + EXPECT_TRUE( + absl::EndsWith(FLAGS_test_flag_07.Filename(), expected_file_name)); + + EXPECT_EQ(FLAGS_test_flag_08.Name(), "test_flag_08"); + EXPECT_EQ(FLAGS_test_flag_08.Help(), "test flag 08"); + EXPECT_TRUE( + absl::EndsWith(FLAGS_test_flag_08.Filename(), expected_file_name)); + + EXPECT_EQ(FLAGS_test_flag_09.Name(), "test_flag_09"); + EXPECT_EQ(FLAGS_test_flag_09.Help(), "test flag 09"); + EXPECT_TRUE( + absl::EndsWith(FLAGS_test_flag_09.Filename(), expected_file_name)); + + EXPECT_EQ(FLAGS_test_flag_10.Name(), "test_flag_10"); + EXPECT_EQ(FLAGS_test_flag_10.Help(), "test flag 10"); + EXPECT_TRUE( + absl::EndsWith(FLAGS_test_flag_10.Filename(), expected_file_name)); + + EXPECT_EQ(FLAGS_test_flag_11.Name(), "test_flag_11"); + EXPECT_EQ(FLAGS_test_flag_11.Help(), "test flag 11"); + EXPECT_TRUE( + absl::EndsWith(FLAGS_test_flag_11.Filename(), expected_file_name)); +} +#endif // !ABSL_FLAGS_STRIP_NAMES + +// -------------------------------------------------------------------- + +TEST(FlagTest, TestDefault) { + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_01), true); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_02), 1234); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_03), -34); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_04), 189); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_05), 10765); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_06), 40000); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_07), -1234567); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_08), 9876543); + EXPECT_NEAR(absl::GetFlag(FLAGS_test_flag_09), -9.876e-50, 1e-55); + EXPECT_NEAR(absl::GetFlag(FLAGS_test_flag_10), 1.234e12f, 1e5f); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_11), ""); +} + +// -------------------------------------------------------------------- + +TEST(FlagTest, TestGetSet) { + absl::SetFlag(&FLAGS_test_flag_01, false); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_01), false); + + absl::SetFlag(&FLAGS_test_flag_02, 321); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_02), 321); + + absl::SetFlag(&FLAGS_test_flag_03, 67); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_03), 67); + + absl::SetFlag(&FLAGS_test_flag_04, 1); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_04), 1); + + absl::SetFlag(&FLAGS_test_flag_05, -908); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_05), -908); + + absl::SetFlag(&FLAGS_test_flag_06, 4001); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_06), 4001); + + absl::SetFlag(&FLAGS_test_flag_07, -23456); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_07), -23456); + + absl::SetFlag(&FLAGS_test_flag_08, 975310); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_08), 975310); + + absl::SetFlag(&FLAGS_test_flag_09, 1.00001); + EXPECT_NEAR(absl::GetFlag(FLAGS_test_flag_09), 1.00001, 1e-10); + + absl::SetFlag(&FLAGS_test_flag_10, -3.54f); + EXPECT_NEAR(absl::GetFlag(FLAGS_test_flag_10), -3.54f, 1e-6f); + + absl::SetFlag(&FLAGS_test_flag_11, "asdf"); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_11), "asdf"); +} + +// -------------------------------------------------------------------- + +int GetDflt1() { return 1; } + +} // namespace + +ABSL_FLAG(int, test_flag_12, GetDflt1(), "test flag 12"); +ABSL_FLAG(std::string, test_flag_13, absl::StrCat("AAA", "BBB"), + "test flag 13"); + +namespace { + +TEST(FlagTest, TestNonConstexprDefault) { + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_12), 1); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_13), "AAABBB"); +} + +// -------------------------------------------------------------------- + +} // namespace + +ABSL_FLAG(bool, test_flag_14, true, absl::StrCat("test ", "flag ", "14")); + +namespace { + +#if !ABSL_FLAGS_STRIP_HELP +TEST(FlagTest, TestNonConstexprHelp) { + EXPECT_EQ(FLAGS_test_flag_14.Help(), "test flag 14"); +} +#endif //! ABSL_FLAGS_STRIP_HELP + +// -------------------------------------------------------------------- + +int cb_test_value = -1; +void TestFlagCB(); + +} // namespace + +ABSL_FLAG(int, test_flag_with_cb, 100, "").OnUpdate(TestFlagCB); + +ABSL_FLAG(int, test_flag_with_lambda_cb, 200, "").OnUpdate([]() { + cb_test_value = absl::GetFlag(FLAGS_test_flag_with_lambda_cb) + + absl::GetFlag(FLAGS_test_flag_with_cb); +}); + +namespace { + +void TestFlagCB() { cb_test_value = absl::GetFlag(FLAGS_test_flag_with_cb); } + +// Tests side-effects of callback invocation. +TEST(FlagTest, CallbackInvocation) { + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_with_cb), 100); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_with_lambda_cb), 200); + EXPECT_EQ(cb_test_value, 300); + + absl::SetFlag(&FLAGS_test_flag_with_cb, 1); + EXPECT_EQ(cb_test_value, 1); + + absl::SetFlag(&FLAGS_test_flag_with_lambda_cb, 3); + EXPECT_EQ(cb_test_value, 4); +} + +// -------------------------------------------------------------------- + +struct CustomUDT { + CustomUDT() : a(1), b(1) {} + CustomUDT(int a_, int b_) : a(a_), b(b_) {} + + friend bool operator==(const CustomUDT& f1, const CustomUDT& f2) { + return f1.a == f2.a && f1.b == f2.b; + } + + int a; + int b; +}; +bool AbslParseFlag(absl::string_view in, CustomUDT* f, std::string*) { + std::vector<absl::string_view> parts = + absl::StrSplit(in, ':', absl::SkipWhitespace()); + + if (parts.size() != 2) return false; + + if (!absl::SimpleAtoi(parts[0], &f->a)) return false; + + if (!absl::SimpleAtoi(parts[1], &f->b)) return false; + + return true; +} +std::string AbslUnparseFlag(const CustomUDT& f) { + return absl::StrCat(f.a, ":", f.b); +} + +} // namespace + +ABSL_FLAG(CustomUDT, test_flag_15, CustomUDT(), "test flag 15"); + +namespace { + +TEST(FlagTest, TestCustomUDT) { + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_15), CustomUDT(1, 1)); + absl::SetFlag(&FLAGS_test_flag_15, CustomUDT(2, 3)); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_15), CustomUDT(2, 3)); +} + +// MSVC produces link error on the type mismatch. +// Linux does not have build errors and validations work as expected. +#if 0 // !defined(_WIN32) && GTEST_HAS_DEATH_TEST + +TEST(Flagtest, TestTypeMismatchValidations) { + // For builtin types, GetFlag() only does validation in debug mode. + EXPECT_DEBUG_DEATH( + absl::GetFlag(FLAGS_mistyped_int_flag), + "Flag 'mistyped_int_flag' is defined as one type and declared " + "as another"); + EXPECT_DEATH(absl::SetFlag(&FLAGS_mistyped_int_flag, 0), + "Flag 'mistyped_int_flag' is defined as one type and declared " + "as another"); + + EXPECT_DEATH(absl::GetFlag(FLAGS_mistyped_string_flag), + "Flag 'mistyped_string_flag' is defined as one type and " + "declared as another"); + EXPECT_DEATH( + absl::SetFlag(&FLAGS_mistyped_string_flag, std::vector<std::string>{}), + "Flag 'mistyped_string_flag' is defined as one type and declared as " + "another"); +} + +#endif + +// -------------------------------------------------------------------- + +// A contrived type that offers implicit and explicit conversion from specific +// source types. +struct ConversionTestVal { + ConversionTestVal() = default; + explicit ConversionTestVal(int a_in) : a(a_in) {} + + enum class ViaImplicitConv { kTen = 10, kEleven }; + // NOLINTNEXTLINE + ConversionTestVal(ViaImplicitConv from) : a(static_cast<int>(from)) {} + + int a; +}; + +bool AbslParseFlag(absl::string_view in, ConversionTestVal* val_out, + std::string*) { + if (!absl::SimpleAtoi(in, &val_out->a)) { + return false; + } + return true; +} +std::string AbslUnparseFlag(const ConversionTestVal& val) { + return absl::StrCat(val.a); +} + +} // namespace + +// Flag default values can be specified with a value that converts to the flag +// value type implicitly. +ABSL_FLAG(ConversionTestVal, test_flag_16, + ConversionTestVal::ViaImplicitConv::kTen, "test flag 16"); + +namespace { + +TEST(FlagTest, CanSetViaImplicitConversion) { + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_16).a, 10); + absl::SetFlag(&FLAGS_test_flag_16, + ConversionTestVal::ViaImplicitConv::kEleven); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_16).a, 11); +} + +// -------------------------------------------------------------------- + +struct NonDfltConstructible { + public: + // This constructor tests that we can initialize the flag with int value + NonDfltConstructible(int i) : value(i) {} // NOLINT + + // This constructor tests that we can't initialize the flag with char value + // but can with explicitly constructed NonDfltConstructible. + explicit NonDfltConstructible(char c) : value(100 + static_cast<int>(c)) {} + + int value; +}; + +bool AbslParseFlag(absl::string_view in, NonDfltConstructible* ndc_out, + std::string*) { + return absl::SimpleAtoi(in, &ndc_out->value); +} +std::string AbslUnparseFlag(const NonDfltConstructible& ndc) { + return absl::StrCat(ndc.value); +} + +} // namespace + +ABSL_FLAG(NonDfltConstructible, ndc_flag1, NonDfltConstructible('1'), + "Flag with non default constructible type"); +ABSL_FLAG(NonDfltConstructible, ndc_flag2, 0, + "Flag with non default constructible type"); + +namespace { + +TEST(FlagTest, TestNonDefaultConstructibleType) { + EXPECT_EQ(absl::GetFlag(FLAGS_ndc_flag1).value, '1' + 100); + EXPECT_EQ(absl::GetFlag(FLAGS_ndc_flag2).value, 0); + + absl::SetFlag(&FLAGS_ndc_flag1, NonDfltConstructible('A')); + absl::SetFlag(&FLAGS_ndc_flag2, 25); + + EXPECT_EQ(absl::GetFlag(FLAGS_ndc_flag1).value, 'A' + 100); + EXPECT_EQ(absl::GetFlag(FLAGS_ndc_flag2).value, 25); +} + +// -------------------------------------------------------------------- + +} // namespace + +ABSL_RETIRED_FLAG(bool, old_bool_flag, true, "old descr"); +ABSL_RETIRED_FLAG(int, old_int_flag, (int)std::sqrt(10), "old descr"); +ABSL_RETIRED_FLAG(std::string, old_str_flag, "", absl::StrCat("old ", "descr")); + +namespace { + +TEST(FlagTest, TestRetiredFlagRegistration) { + bool is_bool = false; + EXPECT_TRUE(flags::IsRetiredFlag("old_bool_flag", &is_bool)); + EXPECT_TRUE(is_bool); + EXPECT_TRUE(flags::IsRetiredFlag("old_int_flag", &is_bool)); + EXPECT_FALSE(is_bool); + EXPECT_TRUE(flags::IsRetiredFlag("old_str_flag", &is_bool)); + EXPECT_FALSE(is_bool); + EXPECT_FALSE(flags::IsRetiredFlag("some_other_flag", &is_bool)); +} + +} // namespace diff --git a/absl/flags/flag_test_defs.cc b/absl/flags/flag_test_defs.cc new file mode 100644 index 000000000000..3366c5803279 --- /dev/null +++ b/absl/flags/flag_test_defs.cc @@ -0,0 +1,22 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file is used to test the mismatch of the flag type between definition +// and declaration. These are definitions. flag_test.cc contains declarations. +#include <string> +#include "absl/flags/flag.h" + +ABSL_FLAG(int, mistyped_int_flag, 0, ""); +ABSL_FLAG(std::string, mistyped_string_flag, "", ""); diff --git a/absl/flags/internal/commandlineflag.cc b/absl/flags/internal/commandlineflag.cc new file mode 100644 index 000000000000..744447219c71 --- /dev/null +++ b/absl/flags/internal/commandlineflag.cc @@ -0,0 +1,383 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/internal/commandlineflag.h" + +#include <cassert> + +#include "absl/base/internal/raw_logging.h" +#include "absl/base/optimization.h" +#include "absl/flags/config.h" +#include "absl/flags/usage_config.h" +#include "absl/strings/str_cat.h" +#include "absl/synchronization/mutex.h" + +namespace absl { +namespace flags_internal { + +// The help message indicating that the commandline flag has been +// 'stripped'. It will not show up when doing "-help" and its +// variants. The flag is stripped if ABSL_FLAGS_STRIP_HELP is set to 1 +// before including absl/flags/flag.h + +// This is used by this file, and also in commandlineflags_reporting.cc +const char kStrippedFlagHelp[] = "\001\002\003\004 (unknown) \004\003\002\001"; + +namespace { + +void StoreAtomic(CommandLineFlag* flag, const void* data, size_t size) { + int64_t t = 0; + assert(size <= sizeof(int64_t)); + memcpy(&t, data, size); + flag->atomic.store(t, std::memory_order_release); +} + +// If the flag has a mutation callback this function invokes it. While the +// callback is being invoked the primary flag's mutex is unlocked and it is +// re-locked back after call to callback is completed. Callback invocation is +// guarded by flag's secondary mutex instead which prevents concurrent callback +// invocation. Note that it is possible for other thread to grab the primary +// lock and update flag's value at any time during the callback invocation. +// This is by design. Callback can get a value of the flag if necessary, but it +// might be different from the value initiated the callback and it also can be +// different by the time the callback invocation is completed. +// Requires that *primary_lock be held in exclusive mode; it may be released +// and reacquired by the implementation. +void InvokeCallback(CommandLineFlag* flag, absl::Mutex* primary_lock) + EXCLUSIVE_LOCKS_REQUIRED(primary_lock) { + if (!flag->callback) return; + + // The callback lock is guaranteed initialized, because *primary_lock exists. + absl::Mutex* callback_mu = &flag->locks->callback_mu; + + // When executing the callback we need the primary flag's mutex to be unlocked + // so that callback can retrieve the flag's value. + primary_lock->Unlock(); + + { + absl::MutexLock lock(callback_mu); + + flag->callback(); + } + + primary_lock->Lock(); +} + +// Currently we only validate flag values for user-defined flag types. +bool ShouldValidateFlagValue(const CommandLineFlag& flag) { +#define DONT_VALIDATE(T) \ + if (flag.IsOfType<T>()) return false; + ABSL_FLAGS_INTERNAL_FOR_EACH_LOCK_FREE(DONT_VALIDATE) + DONT_VALIDATE(std::string) + DONT_VALIDATE(std::vector<std::string>) +#undef DONT_VALIDATE + + return true; +} + +} // namespace + +// Update any copy of the flag value that is stored in an atomic word. +// In addition if flag has a mutation callback this function invokes it. +void UpdateCopy(CommandLineFlag* flag, absl::Mutex* primary_lock) + EXCLUSIVE_LOCKS_REQUIRED(primary_lock) { +#define STORE_ATOMIC(T) \ + else if (flag->IsOfType<T>()) { \ + StoreAtomic(flag, flag->cur, sizeof(T)); \ + } + + if (false) { + } + ABSL_FLAGS_INTERNAL_FOR_EACH_LOCK_FREE(STORE_ATOMIC) +#undef STORE_ATOMIC + + InvokeCallback(flag, primary_lock); +} + +// Ensure that the lazily initialized fields of *flag have been initialized, +// and return &flag->locks->primary_mu. +absl::Mutex* InitFlagIfNecessary(CommandLineFlag* flag) + LOCK_RETURNED(flag->locks->primary_mu) { + absl::Mutex* mu; + if (!flag->inited.load(std::memory_order_acquire)) { + // Need to initialize lazily initialized fields. + ABSL_CONST_INIT static absl::Mutex init_lock(absl::kConstInit); + init_lock.Lock(); + if (flag->locks == nullptr) { // Must initialize Mutexes for this flag. + flag->locks = new flags_internal::CommandLineFlagLocks; + } + mu = &flag->locks->primary_mu; + init_lock.Unlock(); + mu->Lock(); + if (!flag->retired && + flag->def == nullptr) { // Need to initialize def and cur fields. + flag->def = (*flag->make_init_value)(); + flag->cur = Clone(flag->op, flag->def); + UpdateCopy(flag, mu); + } + mu->Unlock(); + flag->inited.store(true, std::memory_order_release); + } else { // All fields initialized; flag->locks is therefore safe to read. + mu = &flag->locks->primary_mu; + } + return mu; +} + +// Return true iff flag value was changed via direct-access. +bool ChangedDirectly(CommandLineFlag* flag, const void* a, const void* b) { + if (!flag->IsAbseilFlag()) { + // Need to compare values for direct-access flags. +#define CHANGED_FOR_TYPE(T) \ + if (flag->IsOfType<T>()) { \ + return *reinterpret_cast<const T*>(a) != *reinterpret_cast<const T*>(b); \ + } + + CHANGED_FOR_TYPE(bool); + CHANGED_FOR_TYPE(int32_t); + CHANGED_FOR_TYPE(int64_t); + CHANGED_FOR_TYPE(uint64_t); + CHANGED_FOR_TYPE(double); + CHANGED_FOR_TYPE(std::string); +#undef CHANGED_FOR_TYPE + } + return false; +} + +// Direct-access flags can be modified without going through the +// flag API. Detect such changes and updated the modified bit. +void UpdateModifiedBit(CommandLineFlag* flag) { + if (!flag->IsAbseilFlag()) { + absl::MutexLock l(InitFlagIfNecessary(flag)); + if (!flag->modified && ChangedDirectly(flag, flag->cur, flag->def)) { + flag->modified = true; + } + } +} + +bool Validate(CommandLineFlag*, const void*) { + return true; +} + +std::string HelpText::GetHelpText() const { + if (help_function_) return help_function_(); + if (help_message_) return help_message_; + + return {}; +} + +const int64_t CommandLineFlag::kAtomicInit; + +void CommandLineFlag::Read(void* dst, + const flags_internal::FlagOpFn dst_op) const { + absl::ReaderMutexLock l( + InitFlagIfNecessary(const_cast<CommandLineFlag*>(this))); + + // `dst_op` is the unmarshaling operation corresponding to the declaration + // visibile at the call site. `op` is the Flag's defined unmarshalling + // operation. They must match for this operation to be well-defined. + if (ABSL_PREDICT_FALSE(dst_op != op)) { + ABSL_INTERNAL_LOG( + ERROR, + absl::StrCat("Flag '", name, + "' is defined as one type and declared as another")); + } + CopyConstruct(op, cur, dst); +} + +void CommandLineFlag::Write(const void* src, + const flags_internal::FlagOpFn src_op) { + absl::Mutex* mu = InitFlagIfNecessary(this); + absl::MutexLock l(mu); + + // `src_op` is the marshalling operation corresponding to the declaration + // visible at the call site. `op` is the Flag's defined marshalling operation. + // They must match for this operation to be well-defined. + if (ABSL_PREDICT_FALSE(src_op != op)) { + ABSL_INTERNAL_LOG( + ERROR, + absl::StrCat("Flag '", name, + "' is defined as one type and declared as another")); + } + + if (ShouldValidateFlagValue(*this)) { + void* obj = Clone(op, src); + std::string ignored_error; + std::string src_as_str = Unparse(marshalling_op, src); + if (!Parse(marshalling_op, src_as_str, obj, &ignored_error) || + !Validate(this, obj)) { + ABSL_INTERNAL_LOG(ERROR, absl::StrCat("Attempt to set flag '", name, + "' to invalid value ", src_as_str)); + } + Delete(op, obj); + } + + modified = true; + counter++; + Copy(op, src, cur); + + UpdateCopy(this, mu); +} + +absl::string_view CommandLineFlag::Typename() const { + // We do not store/report type in Abseil Flags, so that user do not rely on in + // at runtime + if (IsAbseilFlag() || IsRetired()) return ""; + +#define HANDLE_V1_BUILTIN_TYPE(t) \ + if (IsOfType<t>()) { \ + return #t; \ + } + + HANDLE_V1_BUILTIN_TYPE(bool); + HANDLE_V1_BUILTIN_TYPE(int32_t); + HANDLE_V1_BUILTIN_TYPE(int64_t); + HANDLE_V1_BUILTIN_TYPE(uint64_t); + HANDLE_V1_BUILTIN_TYPE(double); +#undef HANDLE_V1_BUILTIN_TYPE + + if (IsOfType<std::string>()) { + return "string"; + } + + return ""; +} + +std::string CommandLineFlag::Filename() const { + return flags_internal::GetUsageConfig().normalize_filename(this->filename); +} + +std::string CommandLineFlag::DefaultValue() const { + return Unparse(this->marshalling_op, this->def); +} + +std::string CommandLineFlag::CurrentValue() const { + return Unparse(this->marshalling_op, this->cur); +} + +void CommandLineFlag::SetCallback( + const flags_internal::FlagCallback mutation_callback) { + absl::Mutex* mu = InitFlagIfNecessary(this); + absl::MutexLock l(mu); + + callback = mutation_callback; + + InvokeCallback(this, mu); +} + +// Attempts to parse supplied `value` string using parsing routine in the `flag` +// argument. If parsing is successful, it will try to validate that the parsed +// value is valid for the specified 'flag'. Finally this function stores the +// parsed value in 'dst' assuming it is a pointer to the flag's value type. In +// case if any error is encountered in either step, the error message is stored +// in 'err' +static bool TryParseLocked(CommandLineFlag* flag, void* dst, + absl::string_view value, std::string* err) { + void* tentative_value = Clone(flag->op, flag->def); + std::string parse_err; + if (!Parse(flag->marshalling_op, value, tentative_value, &parse_err)) { + auto type_name = flag->Typename(); + absl::string_view err_sep = parse_err.empty() ? "" : "; "; + absl::string_view typename_sep = type_name.empty() ? "" : " "; + *err = absl::StrCat("Illegal value '", value, "' specified for", + typename_sep, type_name, " flag '", flag->Name(), "'", + err_sep, parse_err); + Delete(flag->op, tentative_value); + return false; + } + + if (!Validate(flag, tentative_value)) { + *err = absl::StrCat("Failed validation of new value '", + Unparse(flag->marshalling_op, tentative_value), + "' for flag '", flag->Name(), "'"); + Delete(flag->op, tentative_value); + return false; + } + + flag->counter++; + Copy(flag->op, tentative_value, dst); + Delete(flag->op, tentative_value); + return true; +} + +// Sets the value of the flag based on specified string `value`. If the flag +// was successfully set to new value, it returns true. Otherwise, sets `err` +// to indicate the error, leaves the flag unchanged, and returns false. There +// are three ways to set the flag's value: +// * Update the current flag value +// * Update the flag's default value +// * Update the current flag value if it was never set before +// The mode is selected based on 'set_mode' parameter. +bool CommandLineFlag::SetFromString(absl::string_view value, + FlagSettingMode set_mode, + ValueSource source, std::string* err) { + if (IsRetired()) return false; + + UpdateModifiedBit(this); + + absl::Mutex* mu = InitFlagIfNecessary(this); + absl::MutexLock l(mu); + + switch (set_mode) { + case SET_FLAGS_VALUE: { + // set or modify the flag's value + if (!TryParseLocked(this, this->cur, value, err)) return false; + this->modified = true; + UpdateCopy(this, mu); + + if (source == kCommandLine) { + this->on_command_line = true; + } + break; + } + case SET_FLAG_IF_DEFAULT: { + // set the flag's value, but only if it hasn't been set by someone else + if (!this->modified) { + if (!TryParseLocked(this, this->cur, value, err)) return false; + this->modified = true; + UpdateCopy(this, mu); + } else { + // TODO(rogeeff): review and fix this semantic. Currently we do not fail + // in this case if flag is modified. This is misleading since the flag's + // value is not updated even though we return true. + // *err = absl::StrCat(this->Name(), " is already set to ", + // CurrentValue(), "\n"); + // return false; + return true; + } + break; + } + case SET_FLAGS_DEFAULT: { + // modify the flag's default-value + if (!TryParseLocked(this, this->def, value, err)) return false; + + if (!this->modified) { + // Need to set both defvalue *and* current, in this case + Copy(this->op, this->def, this->cur); + UpdateCopy(this, mu); + } + break; + } + default: { + // unknown set_mode + assert(false); + return false; + } + } + + return true; +} + +} // namespace flags_internal +} // namespace absl diff --git a/absl/flags/internal/commandlineflag.h b/absl/flags/internal/commandlineflag.h new file mode 100644 index 000000000000..4815cdc7c9a2 --- /dev/null +++ b/absl/flags/internal/commandlineflag.h @@ -0,0 +1,365 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_FLAGS_INTERNAL_COMMANDLINEFLAG_H_ +#define ABSL_FLAGS_INTERNAL_COMMANDLINEFLAG_H_ + +#include <atomic> + +#include "absl/base/macros.h" +#include "absl/flags/marshalling.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/optional.h" + +namespace absl { +namespace flags_internal { + +// Type-specific operations, eg., parsing, copying, etc. are provided +// by function specific to that type with a signature matching FlagOpFn. +enum FlagOp { + kDelete, + kClone, + kCopy, + kCopyConstruct, + kSizeof, + kParse, + kUnparse +}; +using FlagOpFn = void* (*)(FlagOp, const void*, void*); +using FlagMarshallingOpFn = void* (*)(FlagOp, const void*, void*, void*); + +// Options that control SetCommandLineOptionWithMode. +enum FlagSettingMode { + // update the flag's value unconditionally (can call this multiple times). + SET_FLAGS_VALUE, + // update the flag's value, but *only if* it has not yet been updated + // with SET_FLAGS_VALUE, SET_FLAG_IF_DEFAULT, or "FLAGS_xxx = nondef". + SET_FLAG_IF_DEFAULT, + // set the flag's default value to this. If the flag has not been updated + // yet (via SET_FLAGS_VALUE, SET_FLAG_IF_DEFAULT, or "FLAGS_xxx = nondef") + // change the flag's current value to the new default value as well. + SET_FLAGS_DEFAULT +}; + +// Options that control SetFromString: Source of a value. +enum ValueSource { + // Flag is being set by value specified on a command line. + kCommandLine, + // Flag is being set by value specified in the code. + kProgrammaticChange, +}; + +// Signature for the help generation function used as an argument for the +// absl::Flag constructor. +using HelpGenFunc = std::string (*)(); + +// Signature for the function generating the initial flag value based (usually +// based on default value supplied in flag's definition) +using InitialValGenFunc = void* (*)(); + +// Signature for the mutation callback used by watched Flags +// The callback is noexcept. +// TODO(rogeeff): add noexcept after C++17 support is added. +using FlagCallback = void (*)(); + +extern const char kStrippedFlagHelp[]; + +// The per-type function +template <typename T> +void* FlagOps(FlagOp op, const void* v1, void* v2) { + switch (op) { + case kDelete: + delete static_cast<const T*>(v1); + return nullptr; + case kClone: + return new T(*static_cast<const T*>(v1)); + case kCopy: + *static_cast<T*>(v2) = *static_cast<const T*>(v1); + return nullptr; + case kCopyConstruct: + new (v2) T(*static_cast<const T*>(v1)); + return nullptr; + case kSizeof: + return reinterpret_cast<void*>(sizeof(T)); + default: + return nullptr; + } +} + +template <typename T> +void* FlagMarshallingOps(FlagOp op, const void* v1, void* v2, void* v3) { + switch (op) { + case kParse: { + // initialize the temporary instance of type T based on current value in + // destination (which is going to be flag's default value). + T temp(*static_cast<T*>(v2)); + if (!absl::ParseFlag<T>(*static_cast<const absl::string_view*>(v1), &temp, + static_cast<std::string*>(v3))) { + return nullptr; + } + *static_cast<T*>(v2) = std::move(temp); + return v2; + } + case kUnparse: + *static_cast<std::string*>(v2) = + absl::UnparseFlag<T>(*static_cast<const T*>(v1)); + return nullptr; + default: + return nullptr; + } +} + +// Functions that invoke flag-type-specific operations. +inline void Delete(FlagOpFn op, const void* obj) { + op(flags_internal::kDelete, obj, nullptr); +} + +inline void* Clone(FlagOpFn op, const void* obj) { + return op(flags_internal::kClone, obj, nullptr); +} + +inline void Copy(FlagOpFn op, const void* src, void* dst) { + op(flags_internal::kCopy, src, dst); +} + +inline void CopyConstruct(FlagOpFn op, const void* src, void* dst) { + op(flags_internal::kCopyConstruct, src, dst); +} + +inline bool Parse(FlagMarshallingOpFn op, absl::string_view text, void* dst, + std::string* error) { + return op(flags_internal::kParse, &text, dst, error) != nullptr; +} + +inline std::string Unparse(FlagMarshallingOpFn op, const void* val) { + std::string result; + op(flags_internal::kUnparse, val, &result, nullptr); + return result; +} + +inline size_t Sizeof(FlagOpFn op) { + // This sequence of casts reverses the sequence from base::internal::FlagOps() + return static_cast<size_t>(reinterpret_cast<intptr_t>( + op(flags_internal::kSizeof, nullptr, nullptr))); +} + +// The following struct contains the locks in a CommandLineFlag struct. +// They are in a separate struct that is lazily allocated to avoid problems +// with static initialization and to avoid multiple allocations. +struct CommandLineFlagLocks { + absl::Mutex primary_mu; // protects several fields in CommandLineFlag + absl::Mutex callback_mu; // used to serialize callbacks +}; + +// Holds either a pointer to help text or a function which produces it. This is +// needed for supporting both static initialization of Flags while supporting +// the legacy registration framework. We can't use absl::variant<const char*, +// const char*(*)()> since anybody passing 0 or nullptr in to a CommandLineFlag +// would find an ambiguity. +class HelpText { + public: + static constexpr HelpText FromFunctionPointer(const HelpGenFunc fn) { + return HelpText(fn, nullptr); + } + static constexpr HelpText FromStaticCString(const char* msg) { + return HelpText(nullptr, msg); + } + + std::string GetHelpText() const; + + HelpText() = delete; + HelpText(const HelpText&) = default; + HelpText(HelpText&&) = default; + + private: + explicit constexpr HelpText(const HelpGenFunc fn, const char* msg) + : help_function_(fn), help_message_(msg) {} + + HelpGenFunc help_function_; + const char* help_message_; +}; + +// Holds all information for a flag. +struct CommandLineFlag { + constexpr CommandLineFlag( + const char* name_arg, HelpText help_text, const char* filename_arg, + const flags_internal::FlagOpFn op_arg, + const flags_internal::FlagMarshallingOpFn marshalling_op_arg, + const flags_internal::InitialValGenFunc initial_value_gen, + const bool retired_arg, void* def_arg, void* cur_arg) + : name(name_arg), + help(help_text), + filename(filename_arg), + op(op_arg), + marshalling_op(marshalling_op_arg), + make_init_value(initial_value_gen), + retired(retired_arg), + inited(false), + modified(false), + on_command_line(false), + validator(nullptr), + callback(nullptr), + def(def_arg), + cur(cur_arg), + counter(0), + atomic(kAtomicInit), + locks(nullptr) {} + + // Not copyable/assignable. + CommandLineFlag(const CommandLineFlag&) = delete; + CommandLineFlag& operator=(const CommandLineFlag&) = delete; + + absl::string_view Name() const { return name; } + std::string Help() const { return help.GetHelpText(); } + bool IsRetired() const { return this->retired; } + bool IsSpecifiedOnCommandLine() const { return on_command_line; } + // Returns true iff this is a handle to an Abseil Flag. + bool IsAbseilFlag() const { + // Set to null for V1 flags + return this->make_init_value != nullptr; + } + + absl::string_view Typename() const; + std::string Filename() const; + std::string DefaultValue() const; + std::string CurrentValue() const; + + // Return true iff flag has type T. + template <typename T> + inline bool IsOfType() const { + return this->op == &flags_internal::FlagOps<T>; + } + + // Attempts to retrieve the flag value. Returns value on success, + // absl::nullopt otherwise. + template <typename T> + absl::optional<T> Get() { + if (IsRetired() || flags_internal::FlagOps<T> != this->op) + return absl::nullopt; + + T res; + Read(&res, flags_internal::FlagOps<T>); + + return res; + } + + void SetCallback(const flags_internal::FlagCallback mutation_callback); + + // Sets the value of the flag based on specified std::string `value`. If the flag + // was successfully set to new value, it returns true. Otherwise, sets `error` + // to indicate the error, leaves the flag unchanged, and returns false. There + // are three ways to set the flag's value: + // * Update the current flag value + // * Update the flag's default value + // * Update the current flag value if it was never set before + // The mode is selected based on `set_mode` parameter. + bool SetFromString(absl::string_view value, + flags_internal::FlagSettingMode set_mode, + flags_internal::ValueSource source, std::string* error); + + // Constant configuration for a particular flag. + private: + const char* const name; + const HelpText help; + const char* const filename; + + public: + const FlagOpFn op; // Type-specific handler + const FlagMarshallingOpFn marshalling_op; // Marshalling ops handler + const InitialValGenFunc make_init_value; // Makes initial value for the flag + const bool retired; // Is the flag retired? + std::atomic<bool> inited; // fields have been lazily initialized + + // Mutable state (guarded by locks->primary_mu). + bool modified; // Has flag value been modified? + bool on_command_line; // Specified on command line. + bool (*validator)(); // Validator function, or nullptr + FlagCallback callback; // Mutation callback, or nullptr + void* def; // Lazily initialized pointer to default value + void* cur; // Lazily initialized pointer to current value + int64_t counter; // Mutation counter + + // For some types, a copy of the current value is kept in an atomically + // accessible field. + static const int64_t kAtomicInit = 0xababababababababll; + std::atomic<int64_t> atomic; + + // Lazily initialized mutexes for this flag value. We cannot inline a + // SpinLock or Mutex here because those have non-constexpr constructors and + // so would prevent constant initialization of this type. + // TODO(rogeeff): fix it once Mutex has constexpr constructor + struct CommandLineFlagLocks* locks; // locks, laziliy allocated. + + // copy construct new value of flag's type in a memory referenced by dst + // based on current flag's value + void Read(void* dst, const flags_internal::FlagOpFn dst_op) const; + // updates flag's value to *src (locked) + void Write(const void* src, const flags_internal::FlagOpFn src_op); + + ABSL_DEPRECATED( + "temporary until FlagName call sites are migrated and validator API is " + "changed") + const char* NameAsCString() const { return name; } + + private: + friend class FlagRegistry; +}; + +// Ensure that the lazily initialized fields of *flag have been initialized, +// and return &flag->locks->primary_mu. +absl::Mutex* InitFlagIfNecessary(CommandLineFlag* flag); +// Update any copy of the flag value that is stored in an atomic word. +// In addition if flag has a mutation callback this function invokes it. While +// callback is being invoked the primary flag's mutex is unlocked and it is +// re-locked back after call to callback is completed. Callback invocation is +// guarded by flag's secondary mutex instead which prevents concurrent callback +// invocation. Note that it is possible for other thread to grab the primary +// lock and update flag's value at any time during the callback invocation. +// This is by design. Callback can get a value of the flag if necessary, but it +// might be different from the value initiated the callback and it also can be +// different by the time the callback invocation is completed. +// Requires that *primary_lock be held in exclusive mode; it may be released +// and reacquired by the implementation. +void UpdateCopy(CommandLineFlag* flag, absl::Mutex* primary_lock); +// Return true iff flag value was changed via direct-access. +bool ChangedDirectly(CommandLineFlag* flag, const void* a, const void* b); +// Direct-access flags can be modified without going through the +// flag API. Detect such changes and updated the modified bit. +void UpdateModifiedBit(CommandLineFlag* flag); +// Invoke the flag validators for old flags. +// TODO(rogeeff): implement proper validators for Abseil Flags +bool Validate(CommandLineFlag* flag, const void* value); + +// This macro is the "source of truth" for the list of supported flag types we +// expect to perform lock free operations on. Specifically it generates code, +// a one argument macro operating on a type, supplied as a macro argument, for +// each type in the list. +#define ABSL_FLAGS_INTERNAL_FOR_EACH_LOCK_FREE(A) \ + A(bool) \ + A(short) \ + A(unsigned short) \ + A(int) \ + A(unsigned int) \ + A(long) \ + A(unsigned long) \ + A(long long) \ + A(unsigned long long) \ + A(double) \ + A(float) + +} // namespace flags_internal +} // namespace absl + +#endif // ABSL_FLAGS_INTERNAL_COMMANDLINEFLAG_H_ diff --git a/absl/flags/internal/commandlineflag_test.cc b/absl/flags/internal/commandlineflag_test.cc new file mode 100644 index 000000000000..705f301ce7a0 --- /dev/null +++ b/absl/flags/internal/commandlineflag_test.cc @@ -0,0 +1,196 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/internal/commandlineflag.h" + +#include "gtest/gtest.h" +#include "absl/flags/flag.h" +#include "absl/flags/internal/registry.h" +#include "absl/memory/memory.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" + +ABSL_FLAG(int, int_flag, 201, "int_flag help"); +ABSL_FLAG(std::string, string_flag, "dflt", + absl::StrCat("string_flag", " help")); +ABSL_RETIRED_FLAG(bool, bool_retired_flag, false, "bool_retired_flag help"); + +namespace { + +namespace flags = absl::flags_internal; + +class CommandLineFlagTest : public testing::Test { + protected: + void SetUp() override { flag_saver_ = absl::make_unique<flags::FlagSaver>(); } + void TearDown() override { flag_saver_.reset(); } + + private: + std::unique_ptr<flags::FlagSaver> flag_saver_; +}; + +TEST_F(CommandLineFlagTest, TestAttributesAccessMethods) { + auto* flag_01 = flags::FindCommandLineFlag("int_flag"); + + ASSERT_TRUE(flag_01); + EXPECT_EQ(flag_01->Name(), "int_flag"); + EXPECT_EQ(flag_01->Help(), "int_flag help"); + EXPECT_EQ(flag_01->Typename(), ""); + EXPECT_TRUE(!flag_01->IsRetired()); + EXPECT_TRUE(flag_01->IsOfType<int>()); + EXPECT_TRUE(absl::EndsWith( + flag_01->Filename(), + "absl/flags/internal/commandlineflag_test.cc")); + + auto* flag_02 = flags::FindCommandLineFlag("string_flag"); + + ASSERT_TRUE(flag_02); + EXPECT_EQ(flag_02->Name(), "string_flag"); + EXPECT_EQ(flag_02->Help(), "string_flag help"); + EXPECT_EQ(flag_02->Typename(), ""); + EXPECT_TRUE(!flag_02->IsRetired()); + EXPECT_TRUE(flag_02->IsOfType<std::string>()); + EXPECT_TRUE(absl::EndsWith( + flag_02->Filename(), + "absl/flags/internal/commandlineflag_test.cc")); + + auto* flag_03 = flags::FindRetiredFlag("bool_retired_flag"); + + ASSERT_TRUE(flag_03); + EXPECT_EQ(flag_03->Name(), "bool_retired_flag"); + EXPECT_EQ(flag_03->Help(), ""); + EXPECT_EQ(flag_03->Typename(), ""); + EXPECT_TRUE(flag_03->IsRetired()); + EXPECT_TRUE(flag_03->IsOfType<bool>()); + EXPECT_EQ(flag_03->Filename(), "RETIRED"); +} + +// -------------------------------------------------------------------- + +TEST_F(CommandLineFlagTest, TestValueAccessMethods) { + absl::SetFlag(&FLAGS_int_flag, 301); + auto* flag_01 = flags::FindCommandLineFlag("int_flag"); + + ASSERT_TRUE(flag_01); + EXPECT_EQ(flag_01->CurrentValue(), "301"); + EXPECT_EQ(flag_01->DefaultValue(), "201"); + + absl::SetFlag(&FLAGS_string_flag, "new_str_value"); + auto* flag_02 = flags::FindCommandLineFlag("string_flag"); + + ASSERT_TRUE(flag_02); + EXPECT_EQ(flag_02->CurrentValue(), "new_str_value"); + EXPECT_EQ(flag_02->DefaultValue(), "dflt"); +} + +// -------------------------------------------------------------------- + +TEST_F(CommandLineFlagTest, TestSetFromStringCurrentValue) { + std::string err; + + auto* flag_01 = flags::FindCommandLineFlag("int_flag"); + EXPECT_FALSE(flag_01->on_command_line); + + EXPECT_TRUE(flag_01->SetFromString("11", flags::SET_FLAGS_VALUE, + flags::kProgrammaticChange, &err)); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 11); + EXPECT_FALSE(flag_01->on_command_line); + + EXPECT_TRUE(flag_01->SetFromString("-123", flags::SET_FLAGS_VALUE, + flags::kProgrammaticChange, &err)); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), -123); + EXPECT_FALSE(flag_01->on_command_line); + + EXPECT_TRUE(!flag_01->SetFromString("xyz", flags::SET_FLAGS_VALUE, + flags::kProgrammaticChange, &err)); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), -123); + EXPECT_EQ(err, "Illegal value 'xyz' specified for flag 'int_flag'"); + EXPECT_FALSE(flag_01->on_command_line); + + EXPECT_TRUE(!flag_01->SetFromString("A1", flags::SET_FLAGS_VALUE, + flags::kProgrammaticChange, &err)); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), -123); + EXPECT_EQ(err, "Illegal value 'A1' specified for flag 'int_flag'"); + EXPECT_FALSE(flag_01->on_command_line); + + EXPECT_TRUE(flag_01->SetFromString("0x10", flags::SET_FLAGS_VALUE, + flags::kProgrammaticChange, &err)); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 16); + EXPECT_FALSE(flag_01->on_command_line); + + EXPECT_TRUE(flag_01->SetFromString("011", flags::SET_FLAGS_VALUE, + flags::kCommandLine, &err)); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 11); + EXPECT_TRUE(flag_01->on_command_line); + + EXPECT_TRUE(!flag_01->SetFromString("", flags::SET_FLAGS_VALUE, + flags::kProgrammaticChange, &err)); + EXPECT_EQ(err, "Illegal value '' specified for flag 'int_flag'"); + + auto* flag_02 = flags::FindCommandLineFlag("string_flag"); + EXPECT_TRUE(flag_02->SetFromString("xyz", flags::SET_FLAGS_VALUE, + flags::kProgrammaticChange, &err)); + EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), "xyz"); + + EXPECT_TRUE(flag_02->SetFromString("", flags::SET_FLAGS_VALUE, + flags::kProgrammaticChange, &err)); + EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), ""); +} + +// -------------------------------------------------------------------- + +TEST_F(CommandLineFlagTest, TestSetFromStringDefaultValue) { + std::string err; + + auto* flag_01 = flags::FindCommandLineFlag("int_flag"); + + EXPECT_TRUE(flag_01->SetFromString("111", flags::SET_FLAGS_DEFAULT, + flags::kProgrammaticChange, &err)); + EXPECT_EQ(flag_01->DefaultValue(), "111"); + + auto* flag_02 = flags::FindCommandLineFlag("string_flag"); + + EXPECT_TRUE(flag_02->SetFromString("abc", flags::SET_FLAGS_DEFAULT, + flags::kProgrammaticChange, &err)); + EXPECT_EQ(flag_02->DefaultValue(), "abc"); +} + +// -------------------------------------------------------------------- + +TEST_F(CommandLineFlagTest, TestSetFromStringIfDefault) { + std::string err; + + auto* flag_01 = flags::FindCommandLineFlag("int_flag"); + + EXPECT_TRUE(flag_01->SetFromString("22", flags::SET_FLAG_IF_DEFAULT, + flags::kProgrammaticChange, &err)) + << err; + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 22); + + EXPECT_TRUE(flag_01->SetFromString("33", flags::SET_FLAG_IF_DEFAULT, + flags::kProgrammaticChange, &err)); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 22); + // EXPECT_EQ(err, "ERROR: int_flag is already set to 22"); + + // Reset back to default value + EXPECT_TRUE(flag_01->SetFromString("201", flags::SET_FLAGS_VALUE, + flags::kProgrammaticChange, &err)); + + EXPECT_TRUE(flag_01->SetFromString("33", flags::SET_FLAG_IF_DEFAULT, + flags::kProgrammaticChange, &err)); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 201); + // EXPECT_EQ(err, "ERROR: int_flag is already set to 201"); +} + +} // namespace diff --git a/absl/flags/internal/flag.h b/absl/flags/internal/flag.h new file mode 100644 index 000000000000..6402866f2249 --- /dev/null +++ b/absl/flags/internal/flag.h @@ -0,0 +1,103 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_FLAGS_INTERNAL_FLAG_H_ +#define ABSL_FLAGS_INTERNAL_FLAG_H_ + +#include "absl/flags/internal/commandlineflag.h" +#include "absl/flags/internal/registry.h" + +namespace absl { +namespace flags_internal { + +// This is "unspecified" implementation of absl::Flag<T> type. +template <typename T> +class Flag { + public: + constexpr Flag(const char* name, const flags_internal::HelpGenFunc help_gen, + const char* filename, + const flags_internal::FlagMarshallingOpFn marshalling_op, + const flags_internal::InitialValGenFunc initial_value_gen) + : internal(name, flags_internal::HelpText::FromFunctionPointer(help_gen), + filename, &flags_internal::FlagOps<T>, marshalling_op, + initial_value_gen, + /*retired_arg=*/false, /*def_arg=*/nullptr, + /*cur_arg=*/nullptr) {} + + // Not copyable/assignable. + Flag(const Flag<T>&) = delete; + Flag<T>& operator=(const Flag<T>&) = delete; + + absl::string_view Name() const { return internal.Name(); } + std::string Help() const { return internal.Help(); } + std::string Filename() const { return internal.Filename(); } + + absl::flags_internal::CommandLineFlag internal; + + void SetCallback(const flags_internal::FlagCallback mutation_callback) { + internal.SetCallback(mutation_callback); + } + + private: + // TODO(rogeeff): add these validations once UnparseFlag invocation is fixed + // for built-in types and when we cleanup existing code from operating on + // forward declared types. + // auto IsCopyConstructible(const T& v) -> decltype(T(v)); + // auto HasAbslParseFlag(absl::string_view in, T* dst, std::string* err) + // -> decltype(AbslParseFlag(in, dst, GlobalStringADLGuard(err))); + // auto HasAbslUnparseFlag(const T& v) -> decltype(AbslUnparseFlag(v)); +}; + +// This class facilitates Flag object registration and tail expression-based +// flag definition, for example: +// ABSL_FLAG(int, foo, 42, "Foo help").OnUpdate(NotifyFooWatcher); +template <typename T, bool do_register> +class FlagRegistrar { + public: + explicit FlagRegistrar(Flag<T>* flag) : flag_(flag) { + if (do_register) flags_internal::RegisterCommandLineFlag(&flag_->internal); + } + + FlagRegistrar& OnUpdate(flags_internal::FlagCallback cb) && { + flag_->SetCallback(cb); + return *this; + } + + // Make the registrar "die" gracefully as a bool on a line where registration + // happens. Registrar objects are intended to live only as temporary. + operator bool() const { return true; } // NOLINT + + private: + Flag<T>* flag_; // Flag being registered (not owned). +}; + +// This struct and corresponding overload to MakeDefaultValue are used to +// facilitate usage of {} as default value in ABSL_FLAG macro. +struct EmptyBraces {}; + +template <typename T> +T* MakeFromDefaultValue(T t) { + return new T(std::move(t)); +} + +template <typename T> +T* MakeFromDefaultValue(EmptyBraces) { + return new T; +} + +} // namespace flags_internal +} // namespace absl + +#endif // ABSL_FLAGS_INTERNAL_FLAG_H_ diff --git a/absl/flags/internal/parse.h b/absl/flags/internal/parse.h new file mode 100644 index 000000000000..fd3aca61f408 --- /dev/null +++ b/absl/flags/internal/parse.h @@ -0,0 +1,48 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_FLAGS_INTERNAL_PARSE_H_ +#define ABSL_FLAGS_INTERNAL_PARSE_H_ + +#include <string> +#include <vector> + +#include "absl/flags/declare.h" + +ABSL_DECLARE_FLAG(std::vector<std::string>, flagfile); +ABSL_DECLARE_FLAG(std::vector<std::string>, fromenv); +ABSL_DECLARE_FLAG(std::vector<std::string>, tryfromenv); +ABSL_DECLARE_FLAG(std::vector<std::string>, undefok); + +namespace absl { +namespace flags_internal { + +enum class ArgvListAction { kRemoveParsedArgs, kKeepParsedArgs }; +enum class UsageFlagsAction { kHandleUsage, kIgnoreUsage }; +enum class OnUndefinedFlag { + kIgnoreUndefined, + kReportUndefined, + kAbortIfUndefined +}; + +std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], + ArgvListAction arg_list_act, + UsageFlagsAction usage_flag_act, + OnUndefinedFlag on_undef_flag); + +} // namespace flags_internal +} // namespace absl + +#endif // ABSL_FLAGS_INTERNAL_PARSE_H_ diff --git a/absl/flags/internal/path_util.h b/absl/flags/internal/path_util.h new file mode 100644 index 000000000000..5615c0e6c590 --- /dev/null +++ b/absl/flags/internal/path_util.h @@ -0,0 +1,60 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_FLAGS_INTERNAL_PATH_UTIL_H_ +#define ABSL_FLAGS_INTERNAL_PATH_UTIL_H_ + +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" + +namespace absl { +namespace flags_internal { + +// A portable interface that returns the basename of the filename passed as an +// argument. It is similar to basename(3) +// <https://linux.die.net/man/3/basename>. +// For example: +// flags_internal::Basename("a/b/prog/file.cc") +// returns "file.cc" +// flags_internal::Basename("file.cc") +// returns "file.cc" +inline absl::string_view Basename(absl::string_view filename) { + auto last_slash_pos = filename.find_last_of("/\\"); + + return last_slash_pos == absl::string_view::npos + ? filename + : filename.substr(last_slash_pos + 1); +} + +// A portable interface that returns the directory name of the filename +// passed as an argument, including the trailing slash. +// Returns the empty string if a slash is not found in the input file name. +// For example: +// flags_internal::Package("a/b/prog/file.cc") +// returns "a/b/prog/" +// flags_internal::Package("file.cc") +// returns "" +inline absl::string_view Package(absl::string_view filename) { + auto last_slash_pos = filename.find_last_of("/\\"); + + return last_slash_pos == absl::string_view::npos + ? absl::string_view() + : filename.substr(0, last_slash_pos + 1); +} + +} // namespace flags_internal +} // namespace absl + +#endif // ABSL_FLAGS_INTERNAL_PATH_UTIL_H_ diff --git a/absl/flags/internal/path_util_test.cc b/absl/flags/internal/path_util_test.cc new file mode 100644 index 000000000000..2091373c88ea --- /dev/null +++ b/absl/flags/internal/path_util_test.cc @@ -0,0 +1,46 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/internal/path_util.h" + +#include "gtest/gtest.h" + +namespace { + +namespace flags = absl::flags_internal; + +TEST(FlagsPathUtilTest, TestBasename) { + EXPECT_EQ(flags::Basename(""), ""); + EXPECT_EQ(flags::Basename("a.cc"), "a.cc"); + EXPECT_EQ(flags::Basename("dir/a.cc"), "a.cc"); + EXPECT_EQ(flags::Basename("dir1/dir2/a.cc"), "a.cc"); + EXPECT_EQ(flags::Basename("../dir1/dir2/a.cc"), "a.cc"); + EXPECT_EQ(flags::Basename("/dir1/dir2/a.cc"), "a.cc"); + EXPECT_EQ(flags::Basename("/dir1/dir2/../dir3/a.cc"), "a.cc"); +} + +// -------------------------------------------------------------------- + +TEST(FlagsPathUtilTest, TestPackage) { + EXPECT_EQ(flags::Package(""), ""); + EXPECT_EQ(flags::Package("a.cc"), ""); + EXPECT_EQ(flags::Package("dir/a.cc"), "dir/"); + EXPECT_EQ(flags::Package("dir1/dir2/a.cc"), "dir1/dir2/"); + EXPECT_EQ(flags::Package("../dir1/dir2/a.cc"), "../dir1/dir2/"); + EXPECT_EQ(flags::Package("/dir1/dir2/a.cc"), "/dir1/dir2/"); + EXPECT_EQ(flags::Package("/dir1/dir2/../dir3/a.cc"), "/dir1/dir2/../dir3/"); +} + +} // namespace diff --git a/absl/flags/internal/program_name.cc b/absl/flags/internal/program_name.cc new file mode 100644 index 000000000000..62be645a96ca --- /dev/null +++ b/absl/flags/internal/program_name.cc @@ -0,0 +1,53 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/internal/program_name.h" + +#include <string> + +#include "absl/flags/internal/path_util.h" +#include "absl/synchronization/mutex.h" + +namespace absl { +namespace flags_internal { + +ABSL_CONST_INIT static absl::Mutex program_name_guard(absl::kConstInit); +ABSL_CONST_INIT static std::string* program_name + GUARDED_BY(program_name_guard) = nullptr; + +std::string ProgramInvocationName() { + absl::MutexLock l(&program_name_guard); + + return program_name ? *program_name : "UNKNOWN"; +} + +std::string ShortProgramInvocationName() { + absl::MutexLock l(&program_name_guard); + + return program_name ? std::string(flags_internal::Basename(*program_name)) + : "UNKNOWN"; +} + +void SetProgramInvocationName(absl::string_view prog_name_str) { + absl::MutexLock l(&program_name_guard); + + if (!program_name) + program_name = new std::string(prog_name_str); + else + program_name->assign(prog_name_str.data(), prog_name_str.size()); +} + +} // namespace flags_internal +} // namespace absl diff --git a/absl/flags/internal/program_name.h b/absl/flags/internal/program_name.h new file mode 100644 index 000000000000..326f24bb6533 --- /dev/null +++ b/absl/flags/internal/program_name.h @@ -0,0 +1,47 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_FLAGS_INTERNAL_PROGRAM_NAME_H_ +#define ABSL_FLAGS_INTERNAL_PROGRAM_NAME_H_ + +#include <string> + +#include "absl/strings/string_view.h" + +// -------------------------------------------------------------------- +// Program name + +namespace absl { +namespace flags_internal { + +// Returns program invocation name or "UNKNOWN" if `SetProgramInvocationName()` +// is never called. At the moment this is always set to argv[0] as part of +// library initialization. +std::string ProgramInvocationName(); + +// Returns base name for program invocation name. For example, if +// ProgramInvocationName() == "a/b/mybinary" +// then +// ShortProgramInvocationName() == "mybinary" +std::string ShortProgramInvocationName(); + +// Sets program invocation name to a new value. Should only be called once +// during program initialization, before any threads are spawned. +void SetProgramInvocationName(absl::string_view prog_name_str); + +} // namespace flags_internal +} // namespace absl + +#endif // ABSL_FLAGS_INTERNAL_PROGRAM_NAME_H_ diff --git a/absl/flags/internal/program_name_test.cc b/absl/flags/internal/program_name_test.cc new file mode 100644 index 000000000000..ed69218b8395 --- /dev/null +++ b/absl/flags/internal/program_name_test.cc @@ -0,0 +1,60 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/internal/program_name.h" + +#include "gtest/gtest.h" +#include "absl/strings/match.h" + +namespace { + +namespace flags = absl::flags_internal; + +TEST(FlagsPathUtilTest, TestInitialProgamName) { + flags::SetProgramInvocationName("absl/flags/program_name_test"); + std::string program_name = flags::ProgramInvocationName(); + for (char& c : program_name) + if (c == '\\') c = '/'; + +#if !defined(__wasm__) && !defined(__asmjs__) + const std::string expect_name = "absl/flags/program_name_test"; + const std::string expect_basename = "program_name_test"; +#else + // For targets that generate javascript or webassembly the invocation name + // has the special value below. + const std::string expect_name = "this.program"; + const std::string expect_basename = "this.program"; +#endif + + EXPECT_TRUE(absl::EndsWith(program_name, expect_name)) << program_name; + EXPECT_EQ(flags::ShortProgramInvocationName(), expect_basename); +} + +TEST(FlagsPathUtilTest, TestProgamNameInterfaces) { + flags::SetProgramInvocationName("a/my_test"); + + EXPECT_EQ(flags::ProgramInvocationName(), "a/my_test"); + EXPECT_EQ(flags::ShortProgramInvocationName(), "my_test"); + + absl::string_view not_null_terminated("absl/aaa/bbb"); + not_null_terminated = not_null_terminated.substr(1, 10); + + flags::SetProgramInvocationName(not_null_terminated); + + EXPECT_EQ(flags::ProgramInvocationName(), "bsl/aaa/bb"); + EXPECT_EQ(flags::ShortProgramInvocationName(), "bb"); +} + +} // namespace diff --git a/absl/flags/internal/registry.cc b/absl/flags/internal/registry.cc new file mode 100644 index 000000000000..435c5b0be330 --- /dev/null +++ b/absl/flags/internal/registry.cc @@ -0,0 +1,529 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/internal/registry.h" + +#include "absl/base/call_once.h" +#include "absl/base/dynamic_annotations.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/flags/config.h" +#include "absl/flags/usage_config.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" + +// -------------------------------------------------------------------- +// FlagRegistry implementation +// A FlagRegistry holds all flag objects indexed +// by their names so that if you know a flag's name you can access or +// set it. + +namespace absl { +namespace flags_internal { +namespace { + +void DestroyFlag(CommandLineFlag* flag) NO_THREAD_SAFETY_ANALYSIS { + // Values are heap allocated for retired and Abseil Flags. + if (flag->IsRetired() || flag->IsAbseilFlag()) { + if (flag->cur) Delete(flag->op, flag->cur); + if (flag->def) Delete(flag->op, flag->def); + } + + delete flag->locks; + + // CommandLineFlag handle object is heap allocated for non Abseil Flags. + if (!flag->IsAbseilFlag()) { + delete flag; + } +} + +// -------------------------------------------------------------------- +// FlagRegistry +// A FlagRegistry singleton object holds all flag objects indexed +// by their names so that if you know a flag's name (as a C +// string), you can access or set it. If the function is named +// FooLocked(), you must own the registry lock before calling +// the function; otherwise, you should *not* hold the lock, and +// the function will acquire it itself if needed. +// -------------------------------------------------------------------- + +// A map from flag pointer to CommandLineFlag*. Used when registering +// validators. +class FlagPtrMap { + public: + void Register(CommandLineFlag* flag) { + auto& vec = buckets_[BucketForFlag(flag->cur)]; + if (vec.size() == vec.capacity()) { + // Bypass default 2x growth factor with 1.25 so we have fuller vectors. + // This saves 4% memory compared to default growth. + vec.reserve(vec.size() * 1.25 + 0.5); + } + vec.push_back(flag); + } + + CommandLineFlag* FindByPtr(const void* flag_ptr) { + const auto& flag_vector = buckets_[BucketForFlag(flag_ptr)]; + for (CommandLineFlag* entry : flag_vector) { + if (entry->cur == flag_ptr) { + return entry; + } + } + return nullptr; + } + + private: + // Instead of std::map, we use a custom hash table where each bucket stores + // flags in a vector. This reduces memory usage 40% of the memory that would + // have been used by std::map. + // + // kNumBuckets was picked as a large enough prime. As of writing this code, a + // typical large binary has ~8k (old-style) flags, and this would gives + // buckets with roughly 50 elements each. + // + // Note that reads to this hash table are rare: exactly as many as we have + // flags with validators. As of writing, a typical binary only registers 52 + // validated flags. + static constexpr size_t kNumBuckets = 163; + std::vector<CommandLineFlag*> buckets_[kNumBuckets]; + + static int BucketForFlag(const void* ptr) { + // Modulo a prime is good enough here. On a real program, bucket size stddev + // after registering 8k flags is ~5 (mean size at 51). + return reinterpret_cast<uintptr_t>(ptr) % kNumBuckets; + } +}; +constexpr size_t FlagPtrMap::kNumBuckets; + +} // namespace + +class FlagRegistry { + public: + FlagRegistry() = default; + ~FlagRegistry() { + for (auto& p : flags_) { + DestroyFlag(p.second); + } + } + + // Store a flag in this registry. Takes ownership of *flag. + // If ptr is non-null, the flag can later be found by calling + // FindFlagViaPtrLocked(ptr). + void RegisterFlag(CommandLineFlag* flag, const void* ptr); + + void Lock() EXCLUSIVE_LOCK_FUNCTION(lock_) { lock_.Lock(); } + void Unlock() UNLOCK_FUNCTION(lock_) { lock_.Unlock(); } + + // Returns the flag object for the specified name, or nullptr if not found. + // Will emit a warning if a 'retired' flag is specified. + CommandLineFlag* FindFlagLocked(absl::string_view name); + + // Returns the retired flag object for the specified name, or nullptr if not + // found or not retired. Does not emit a warning. + CommandLineFlag* FindRetiredFlagLocked(absl::string_view name); + + // Returns the flag object whose current-value is stored at flag_ptr. + CommandLineFlag* FindFlagViaPtrLocked(const void* flag_ptr); + + static FlagRegistry* GlobalRegistry(); // returns a singleton registry + + private: + friend class FlagSaverImpl; // reads all the flags in order to copy them + friend void ForEachFlagUnlocked( + std::function<void(CommandLineFlag*)> visitor); + + // The map from name to flag, for FindFlagLocked(). + using FlagMap = std::map<absl::string_view, CommandLineFlag*>; + using FlagIterator = FlagMap::iterator; + using FlagConstIterator = FlagMap::const_iterator; + FlagMap flags_; + + FlagPtrMap flag_ptr_map_; + + static FlagRegistry* global_registry_; // a singleton registry + + static absl::once_flag global_registry_once_; + + static void InitGlobalRegistry(); + + absl::Mutex lock_; + + // Disallow + FlagRegistry(const FlagRegistry&); + FlagRegistry& operator=(const FlagRegistry&); +}; + +// Get the singleton FlagRegistry object +FlagRegistry* FlagRegistry::global_registry_ = nullptr; +absl::once_flag FlagRegistry::global_registry_once_; + +void FlagRegistry::InitGlobalRegistry() { global_registry_ = new FlagRegistry; } + +FlagRegistry* FlagRegistry::GlobalRegistry() { + absl::call_once(global_registry_once_, &InitGlobalRegistry); + + return global_registry_; +} + +namespace { + +class FlagRegistryLock { + public: + explicit FlagRegistryLock(FlagRegistry* fr) : fr_(fr) { fr_->Lock(); } + ~FlagRegistryLock() { fr_->Unlock(); } + + private: + FlagRegistry* const fr_; +}; + +} // namespace + +void FlagRegistry::RegisterFlag(CommandLineFlag* flag, const void* ptr) { + FlagRegistryLock registry_lock(this); + std::pair<FlagIterator, bool> ins = + flags_.insert(FlagMap::value_type(flag->Name(), flag)); + if (ins.second == false) { // means the name was already in the map + CommandLineFlag* old_flag = ins.first->second; + if (flag->IsRetired() != old_flag->IsRetired()) { + // All registrations must agree on the 'retired' flag. + flags_internal::ReportUsageError( + absl::StrCat( + "Retired flag '", flag->Name(), + "' was defined normally in file '", + (flag->IsRetired() ? old_flag->Filename() : flag->Filename()), + "'."), + true); + } else if (flag->op != old_flag->op) { + flags_internal::ReportUsageError( + absl::StrCat("Flag '", flag->Name(), + "' was defined more than once but with " + "differing types. Defined in files '", + old_flag->Filename(), "' and '", flag->Filename(), + "' with types '", old_flag->Typename(), "' and '", + flag->Typename(), "', respectively."), + true); + } else if (old_flag->IsRetired()) { + // Retired definitions are idempotent. Just keep the old one. + DestroyFlag(flag); + return; + } else if (old_flag->Filename() != flag->Filename()) { + flags_internal::ReportUsageError( + absl::StrCat("Flag '", flag->Name(), + "' was defined more than once (in files '", + old_flag->Filename(), "' and '", flag->Filename(), + "')."), + true); + } else { + flags_internal::ReportUsageError( + absl::StrCat( + "Something wrong with flag '", flag->Name(), "' in file '", + flag->Filename(), "'. One possibility: file '", flag->Filename(), + "' is being linked both statically and dynamically into this " + "executable. e.g. some files listed as srcs to a test and also " + "listed as srcs of some shared lib deps of the same test."), + true); + } + // All cases above are fatal, except for the retired flags. + std::exit(1); + } + + if (ptr != nullptr) { + // This must be the first time we're seeing this flag. + flag_ptr_map_.Register(flag); + } +} + +CommandLineFlag* FlagRegistry::FindFlagLocked(absl::string_view name) { + FlagConstIterator i = flags_.find(name); + if (i == flags_.end()) { + return nullptr; + } + + if (i->second->IsRetired()) { + flags_internal::ReportUsageError( + absl::StrCat("Accessing retired flag '", name, "'"), false); + } + + return i->second; +} + +CommandLineFlag* FlagRegistry::FindRetiredFlagLocked(absl::string_view name) { + FlagConstIterator i = flags_.find(name); + if (i == flags_.end() || !i->second->IsRetired()) { + return nullptr; + } + + return i->second; +} + +CommandLineFlag* FlagRegistry::FindFlagViaPtrLocked(const void* flag_ptr) { + return flag_ptr_map_.FindByPtr(flag_ptr); +} + +// -------------------------------------------------------------------- +// FlagSaver +// FlagSaverImpl +// This class stores the states of all flags at construct time, +// and restores all flags to that state at destruct time. +// Its major implementation challenge is that it never modifies +// pointers in the 'main' registry, so global FLAG_* vars always +// point to the right place. +// -------------------------------------------------------------------- + +class FlagSaverImpl { + public: + // Constructs an empty FlagSaverImpl object. + FlagSaverImpl() {} + ~FlagSaverImpl() { + // reclaim memory from each of our CommandLineFlags + for (const SavedFlag& src : backup_registry_) { + Delete(src.op, src.current); + Delete(src.op, src.default_value); + } + } + + // Saves the flag states from the flag registry into this object. + // It's an error to call this more than once. + // Must be called when the registry mutex is not held. + void SaveFromRegistry() { + assert(backup_registry_.empty()); // call only once! + SavedFlag saved; + flags_internal::ForEachFlag([&](flags_internal::CommandLineFlag* flag) { + if (flag->IsRetired()) return; + + saved.name = flag->Name(); + saved.op = flag->op; + saved.marshalling_op = flag->marshalling_op; + { + absl::MutexLock l(InitFlagIfNecessary(flag)); + saved.validator = flag->validator; + saved.modified = flag->modified; + saved.on_command_line = flag->IsSpecifiedOnCommandLine(); + saved.current = Clone(saved.op, flag->cur); + saved.default_value = Clone(saved.op, flag->def); + saved.counter = flag->counter; + } + backup_registry_.push_back(saved); + }); + } + + // Restores the saved flag states into the flag registry. We + // assume no flags were added or deleted from the registry since + // the SaveFromRegistry; if they were, that's trouble! Must be + // called when the registry mutex is not held. + void RestoreToRegistry() { + FlagRegistry* const global_registry = FlagRegistry::GlobalRegistry(); + FlagRegistryLock frl(global_registry); + for (const SavedFlag& src : backup_registry_) { + CommandLineFlag* flag = global_registry->FindFlagLocked(src.name); + // If null, flag got deleted from registry. + if (!flag) continue; + + bool restored = false; + { + absl::Mutex* mu = InitFlagIfNecessary(flag); + absl::MutexLock l(mu); + flag->validator = src.validator; + flag->modified = src.modified; + flag->on_command_line = src.on_command_line; + if (flag->counter != src.counter || + ChangedDirectly(flag, src.default_value, flag->def)) { + flag->counter++; + Copy(src.op, src.default_value, flag->def); + } + if (flag->counter != src.counter || + ChangedDirectly(flag, src.current, flag->cur)) { + restored = true; + flag->counter++; + Copy(src.op, src.current, flag->cur); + UpdateCopy(flag, mu); + + // Revalidate the flag because the validator might store state based + // on the flag's value, which just changed due to the restore. + // Failing validation is ignored because it's assumed that the flag + // was valid previously and there's little that can be done about it + // here, anyway. + Validate(flag, flag->cur); + } + } + + // Log statements must be done when no flag lock is held. + if (restored) { + ABSL_INTERNAL_LOG( + INFO, absl::StrCat("Restore saved value of ", flag->Name(), ": ", + Unparse(src.marshalling_op, src.current))); + } + } + } + + private: + struct SavedFlag { + absl::string_view name; + FlagOpFn op; + FlagMarshallingOpFn marshalling_op; + int64_t counter; + bool modified; + bool on_command_line; + bool (*validator)(); + const void* current; // nullptr after restore + const void* default_value; // nullptr after restore + }; + + std::vector<SavedFlag> backup_registry_; + + FlagSaverImpl(const FlagSaverImpl&); // no copying! + void operator=(const FlagSaverImpl&); +}; + +FlagSaver::FlagSaver() : impl_(new FlagSaverImpl()) { + impl_->SaveFromRegistry(); +} + +void FlagSaver::Ignore() { + delete impl_; + impl_ = nullptr; +} + +FlagSaver::~FlagSaver() { + if (!impl_) return; + + impl_->RestoreToRegistry(); + delete impl_; +} + +// -------------------------------------------------------------------- +// GetAllFlags() +// The main way the FlagRegistry class exposes its data. This +// returns, as strings, all the info about all the flags in +// the main registry, sorted first by filename they are defined +// in, and then by flagname. +// -------------------------------------------------------------------- + +struct FilenameFlagnameLess { + bool operator()(const CommandLineFlagInfo& a, + const CommandLineFlagInfo& b) const { + int cmp = absl::string_view(a.filename).compare(b.filename); + if (cmp != 0) return cmp < 0; + return a.name < b.name; + } +}; + +void FillCommandLineFlagInfo(CommandLineFlag* flag, + CommandLineFlagInfo* result) { + result->name = std::string(flag->Name()); + result->type = std::string(flag->Typename()); + result->description = flag->Help(); + result->filename = flag->Filename(); + + UpdateModifiedBit(flag); + + absl::MutexLock l(InitFlagIfNecessary(flag)); + result->current_value = flag->CurrentValue(); + result->default_value = flag->DefaultValue(); + result->is_default = !flag->modified; + result->has_validator_fn = (flag->validator != nullptr); + result->flag_ptr = flag->IsAbseilFlag() ? nullptr : flag->cur; +} + +// -------------------------------------------------------------------- + +CommandLineFlag* FindCommandLineFlag(absl::string_view name) { + if (name.empty()) return nullptr; + FlagRegistry* const registry = FlagRegistry::GlobalRegistry(); + FlagRegistryLock frl(registry); + + return registry->FindFlagLocked(name); +} + +CommandLineFlag* FindCommandLineV1Flag(const void* flag_ptr) { + FlagRegistry* const registry = FlagRegistry::GlobalRegistry(); + FlagRegistryLock frl(registry); + + return registry->FindFlagViaPtrLocked(flag_ptr); +} + +CommandLineFlag* FindRetiredFlag(absl::string_view name) { + FlagRegistry* const registry = FlagRegistry::GlobalRegistry(); + FlagRegistryLock frl(registry); + + return registry->FindRetiredFlagLocked(name); +} + +// -------------------------------------------------------------------- + +void ForEachFlagUnlocked(std::function<void(CommandLineFlag*)> visitor) { + FlagRegistry* const registry = FlagRegistry::GlobalRegistry(); + for (FlagRegistry::FlagConstIterator i = registry->flags_.begin(); + i != registry->flags_.end(); ++i) { + visitor(i->second); + } +} + +void ForEachFlag(std::function<void(CommandLineFlag*)> visitor) { + FlagRegistry* const registry = FlagRegistry::GlobalRegistry(); + FlagRegistryLock frl(registry); + ForEachFlagUnlocked(visitor); +} + +// -------------------------------------------------------------------- + +void GetAllFlags(std::vector<CommandLineFlagInfo>* OUTPUT) { + flags_internal::ForEachFlag([&](CommandLineFlag* flag) { + if (flag->IsRetired()) return; + + CommandLineFlagInfo fi; + FillCommandLineFlagInfo(flag, &fi); + OUTPUT->push_back(fi); + }); + + // Now sort the flags, first by filename they occur in, then alphabetically + std::sort(OUTPUT->begin(), OUTPUT->end(), FilenameFlagnameLess()); +} + +// -------------------------------------------------------------------- + +bool RegisterCommandLineFlag(CommandLineFlag* flag, const void* ptr) { + FlagRegistry::GlobalRegistry()->RegisterFlag(flag, ptr); + return true; +} + +// -------------------------------------------------------------------- + +bool Retire(FlagOpFn ops, FlagMarshallingOpFn marshalling_ops, + const char* name) { + auto* flag = new CommandLineFlag( + name, + /*help_text=*/absl::flags_internal::HelpText::FromStaticCString(nullptr), + /*filename_arg=*/"RETIRED", ops, marshalling_ops, + /*initial_value_gen=*/nullptr, + /*retired_arg=*/true, nullptr, nullptr); + FlagRegistry::GlobalRegistry()->RegisterFlag(flag, nullptr); + return true; +} + +// -------------------------------------------------------------------- + +bool IsRetiredFlag(absl::string_view name, bool* type_is_bool) { + assert(!name.empty()); + CommandLineFlag* flag = flags_internal::FindRetiredFlag(name); + if (flag == nullptr) { + return false; + } + assert(type_is_bool); + *type_is_bool = flag->IsOfType<bool>(); + return true; +} + +} // namespace flags_internal +} // namespace absl diff --git a/absl/flags/internal/registry.h b/absl/flags/internal/registry.h new file mode 100644 index 000000000000..bd141e1e23d2 --- /dev/null +++ b/absl/flags/internal/registry.h @@ -0,0 +1,168 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_FLAGS_INTERNAL_REGISTRY_H_ +#define ABSL_FLAGS_INTERNAL_REGISTRY_H_ + +#include <functional> +#include <map> +#include <string> + +#include "absl/base/macros.h" +#include "absl/flags/internal/commandlineflag.h" + +// -------------------------------------------------------------------- +// Global flags registry API. + +namespace absl { +namespace flags_internal { + +// CommandLineFlagInfo holds all information for a flag. +struct CommandLineFlagInfo { + std::string name; // the name of the flag + std::string type; // DO NOT use. Use flag->IsOfType<T>() instead. + std::string description; // the "help text" associated with the flag + std::string current_value; // the current value, as a std::string + std::string default_value; // the default value, as a std::string + std::string filename; // 'cleaned' version of filename holding the flag + bool has_validator_fn; // true if RegisterFlagValidator called on this flag + + bool is_default; // true if the flag has the default value and + // has not been set explicitly from the cmdline + // or via SetCommandLineOption. + + // nullptr for ABSL_FLAG. A pointer to the flag's current value + // otherwise. E.g., for DEFINE_int32(foo, ...), flag_ptr will be + // &FLAGS_foo. + const void* flag_ptr; +}; + +//----------------------------------------------------------------------------- + +void FillCommandLineFlagInfo(CommandLineFlag* flag, + CommandLineFlagInfo* result); + +//----------------------------------------------------------------------------- + +CommandLineFlag* FindCommandLineFlag(absl::string_view name); +CommandLineFlag* FindCommandLineV1Flag(const void* flag_ptr); +CommandLineFlag* FindRetiredFlag(absl::string_view name); + +// Executes specified visitor for each non-retired flag in the registry. +// Requires the caller hold the registry lock. +void ForEachFlagUnlocked(std::function<void(CommandLineFlag*)> visitor); +// Executes specified visitor for each non-retired flag in the registry. While +// callback are executed, the registry is locked and can't be changed. +void ForEachFlag(std::function<void(CommandLineFlag*)> visitor); + +//----------------------------------------------------------------------------- + +// Store the list of all flags in *OUTPUT, sorted by file. +void GetAllFlags(std::vector<CommandLineFlagInfo>* OUTPUT); + +//----------------------------------------------------------------------------- + +bool RegisterCommandLineFlag(CommandLineFlag*, const void* ptr = nullptr); + +//----------------------------------------------------------------------------- +// Retired registrations: +// +// Retired flag registrations are treated specially. A 'retired' flag is +// provided only for compatibility with automated invocations that still +// name it. A 'retired' flag: +// - is not bound to a C++ FLAGS_ reference. +// - has a type and a value, but that value is intentionally inaccessible. +// - does not appear in --help messages. +// - is fully supported by _all_ flag parsing routines. +// - consumes args normally, and complains about type mismatches in its +// argument. +// - emits a complaint but does not die (e.g. LOG(ERROR)) if it is +// accessed by name through the flags API for parsing or otherwise. +// +// The registrations for a flag happen in an unspecified order as the +// initializers for the namespace-scope objects of a program are run. +// Any number of weak registrations for a flag can weakly define the flag. +// One non-weak registration will upgrade the flag from weak to non-weak. +// Further weak registrations of a non-weak flag are ignored. +// +// This mechanism is designed to support moving dead flags into a +// 'graveyard' library. An example migration: +// +// 0: Remove references to this FLAGS_flagname in the C++ codebase. +// 1: Register as 'retired' in old_lib. +// 2: Make old_lib depend on graveyard. +// 3: Add a redundant 'retired' registration to graveyard. +// 4: Remove the old_lib 'retired' registration. +// 5: Eventually delete the graveyard registration entirely. +// +// Returns bool to enable use in namespace-scope initializers. +// For example: +// +// static const bool dummy = base::RetiredFlag<int32_t>("myflag"); +// +// Or to declare several at once: +// +// static bool dummies[] = { +// base::RetiredFlag<std::string>("some_string_flag"), +// base::RetiredFlag<double>("some_double_flag"), +// base::RetiredFlag<int32_t>("some_int32_flag") +// }; + +// Retire flag with name "name" and type indicated by ops. +bool Retire(FlagOpFn ops, FlagMarshallingOpFn marshalling_ops, + const char* name); + +// Registered a retired flag with name 'flag_name' and type 'T'. +template <typename T> +inline bool RetiredFlag(const char* flag_name) { + return flags_internal::Retire(flags_internal::FlagOps<T>, + flags_internal::FlagMarshallingOps<T>, + flag_name); +} + +// If the flag is retired, returns true and indicates in |*type_is_bool| +// whether the type of the retired flag is a bool. +// Only to be called by code that needs to explicitly ignore retired flags. +bool IsRetiredFlag(absl::string_view name, bool* type_is_bool); + +//----------------------------------------------------------------------------- +// Saves the states (value, default value, whether the user has set +// the flag, registered validators, etc) of all flags, and restores +// them when the FlagSaver is destroyed. +// +// This class is thread-safe. However, its destructor writes to +// exactly the set of flags that have changed value during its +// lifetime, so concurrent _direct_ access to those flags +// (i.e. FLAGS_foo instead of {Get,Set}CommandLineOption()) is unsafe. + +class FlagSaver { + public: + FlagSaver(); + ~FlagSaver(); + + FlagSaver(const FlagSaver&) = delete; + void operator=(const FlagSaver&) = delete; + + // Prevents saver from restoring the saved state of flags. + void Ignore(); + + private: + class FlagSaverImpl* impl_; // we use pimpl here to keep API steady +}; + +} // namespace flags_internal +} // namespace absl + +#endif // ABSL_FLAGS_INTERNAL_REGISTRY_H_ diff --git a/absl/flags/internal/type_erased.cc b/absl/flags/internal/type_erased.cc new file mode 100644 index 000000000000..cc103983672b --- /dev/null +++ b/absl/flags/internal/type_erased.cc @@ -0,0 +1,121 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/internal/type_erased.h" + +#include "absl/base/internal/raw_logging.h" +#include "absl/flags/config.h" +#include "absl/flags/usage_config.h" +#include "absl/strings/str_cat.h" + +namespace absl { +namespace flags_internal { + +bool GetCommandLineOption(absl::string_view name, std::string* value) { + if (name.empty()) return false; + assert(value); + + CommandLineFlag* flag = flags_internal::FindCommandLineFlag(name); + if (flag == nullptr || flag->IsRetired()) { + return false; + } + + absl::MutexLock l(InitFlagIfNecessary(flag)); + *value = flag->CurrentValue(); + return true; +} + +bool GetCommandLineFlagInfo(absl::string_view name, + CommandLineFlagInfo* OUTPUT) { + if (name.empty()) return false; + + CommandLineFlag* flag = flags_internal::FindCommandLineFlag(name); + if (flag == nullptr || flag->IsRetired()) { + return false; + } + + assert(OUTPUT); + FillCommandLineFlagInfo(flag, OUTPUT); + return true; +} + +CommandLineFlagInfo GetCommandLineFlagInfoOrDie(absl::string_view name) { + CommandLineFlagInfo info; + if (!GetCommandLineFlagInfo(name, &info)) { + ABSL_INTERNAL_LOG(FATAL, absl::StrCat("Flag '", name, "' does not exist")); + } + return info; +} + +// -------------------------------------------------------------------- + +bool SetCommandLineOption(absl::string_view name, absl::string_view value) { + return SetCommandLineOptionWithMode(name, value, + flags_internal::SET_FLAGS_VALUE); +} + +bool SetCommandLineOptionWithMode(absl::string_view name, + absl::string_view value, + FlagSettingMode set_mode) { + CommandLineFlag* flag = flags_internal::FindCommandLineFlag(name); + + if (!flag || flag->IsRetired()) return false; + + std::string error; + if (!flag->SetFromString(value, set_mode, kProgrammaticChange, &error)) { + // Errors here are all of the form: the provided name was a recognized + // flag, but the value was invalid (bad type, or validation failed). + flags_internal::ReportUsageError(error, false); + return false; + } + + return true; +} + +// -------------------------------------------------------------------- + +bool IsValidFlagValue(absl::string_view name, absl::string_view value) { + CommandLineFlag* flag = flags_internal::FindCommandLineFlag(name); + if (flag == nullptr) { + return false; + } + + if (flag->IsRetired()) { + return true; + } + + // No need to lock the flag since we are not mutating it. + void* obj = Clone(flag->op, flag->def); + std::string ignored_error; + const bool result = + flags_internal::Parse(flag->marshalling_op, value, obj, &ignored_error) && + Validate(flag, obj); + Delete(flag->op, obj); + return result; +} + +// -------------------------------------------------------------------- + +bool SpecifiedOnCommandLine(absl::string_view name) { + CommandLineFlag* flag = flags_internal::FindCommandLineFlag(name); + if (flag != nullptr && !flag->IsRetired()) { + absl::MutexLock l(InitFlagIfNecessary(flag)); + return flag->IsSpecifiedOnCommandLine(); + } + return false; +} + +} // namespace flags_internal +} // namespace absl diff --git a/absl/flags/internal/type_erased.h b/absl/flags/internal/type_erased.h new file mode 100644 index 000000000000..249d36b9f33f --- /dev/null +++ b/absl/flags/internal/type_erased.h @@ -0,0 +1,97 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_FLAGS_INTERNAL_TYPE_ERASED_H_ +#define ABSL_FLAGS_INTERNAL_TYPE_ERASED_H_ + +#include <string> + +#include "absl/flags/internal/commandlineflag.h" +#include "absl/flags/internal/registry.h" + +// -------------------------------------------------------------------- +// Registry interfaces operating on type erased handles. + +namespace absl { +namespace flags_internal { + +// If a flag named "name" exists, store its current value in *OUTPUT +// and return true. Else return false without changing *OUTPUT. +// Thread-safe. +bool GetCommandLineOption(absl::string_view name, std::string* value); + +// If a flag named "name" exists, store its information in *OUTPUT +// and return true. Else return false without changing *OUTPUT. +// Thread-safe. +bool GetCommandLineFlagInfo(absl::string_view name, + CommandLineFlagInfo* OUTPUT); + +// Returns the CommandLineFlagInfo of the flagname. exit() with an +// error code if name not found. +// Thread-safe. +CommandLineFlagInfo GetCommandLineFlagInfoOrDie(absl::string_view name); + +// Set the value of the flag named "name" to value. If successful, +// returns true. If not successful (e.g., the flag was not found or +// the value is not a valid value), returns false. +// Thread-safe. +bool SetCommandLineOption(absl::string_view name, absl::string_view value); + +bool SetCommandLineOptionWithMode(absl::string_view name, + absl::string_view value, + FlagSettingMode set_mode); + +//----------------------------------------------------------------------------- + +// Returns true iff all of the following conditions are true: +// (a) "name" names a registered flag +// (b) "value" can be parsed succesfully according to the type of the flag +// (c) parsed value passes any validator associated with the flag +bool IsValidFlagValue(absl::string_view name, absl::string_view value); + +//----------------------------------------------------------------------------- + +// Returns true iff a flag named "name" was specified on the command line +// (either directly, or via one of --flagfile or --fromenv or --tryfromenv). +// +// Any non-command-line modification of the flag does not affect the +// result of this function. So for example, if a flag was passed on +// the command line but then reset via SET_FLAGS_DEFAULT, this +// function will still return true. +bool SpecifiedOnCommandLine(absl::string_view name); + +//----------------------------------------------------------------------------- + +// If a flag with specified "name" exists and has type T, store +// its current value in *dst and return true. Else return false +// without touching *dst. T must obey all of the requirements for +// types passed to DEFINE_FLAG. +template <typename T> +inline bool GetByName(absl::string_view name, T* dst) { + CommandLineFlag* flag = flags_internal::FindCommandLineFlag(name); + if (!flag) return false; + + if (auto val = flag->Get<T>()) { + *dst = *val; + return true; + } + + return false; +} + +} // namespace flags_internal +} // namespace absl + +#endif // ABSL_FLAGS_INTERNAL_TYPE_ERASED_H_ diff --git a/absl/flags/internal/type_erased_test.cc b/absl/flags/internal/type_erased_test.cc new file mode 100644 index 000000000000..ac749a607548 --- /dev/null +++ b/absl/flags/internal/type_erased_test.cc @@ -0,0 +1,147 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/internal/type_erased.h" + +#include <cmath> + +#include "gtest/gtest.h" +#include "absl/flags/flag.h" +#include "absl/memory/memory.h" +#include "absl/strings/str_cat.h" + +ABSL_FLAG(int, int_flag, 1, "int_flag help"); +ABSL_FLAG(std::string, string_flag, "dflt", "string_flag help"); +ABSL_RETIRED_FLAG(bool, bool_retired_flag, false, "bool_retired_flag help"); + +namespace { + +namespace flags = absl::flags_internal; + +class TypeErasedTest : public testing::Test { + protected: + void SetUp() override { flag_saver_ = absl::make_unique<flags::FlagSaver>(); } + void TearDown() override { flag_saver_.reset(); } + + private: + std::unique_ptr<flags::FlagSaver> flag_saver_; +}; + +// -------------------------------------------------------------------- + +TEST_F(TypeErasedTest, TestGetCommandLineOption) { + std::string value; + EXPECT_TRUE(flags::GetCommandLineOption("int_flag", &value)); + EXPECT_EQ(value, "1"); + + EXPECT_TRUE(flags::GetCommandLineOption("string_flag", &value)); + EXPECT_EQ(value, "dflt"); + + EXPECT_FALSE(flags::GetCommandLineOption("bool_retired_flag", &value)); + + EXPECT_FALSE(flags::GetCommandLineOption("unknown_flag", &value)); +} + +// -------------------------------------------------------------------- + +TEST_F(TypeErasedTest, TestSetCommandLineOption) { + EXPECT_TRUE(flags::SetCommandLineOption("int_flag", "101")); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 101); + + EXPECT_TRUE(flags::SetCommandLineOption("string_flag", "asdfgh")); + EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), "asdfgh"); + + EXPECT_FALSE(flags::SetCommandLineOption("bool_retired_flag", "true")); + + EXPECT_FALSE(flags::SetCommandLineOption("unknown_flag", "true")); +} + +// -------------------------------------------------------------------- + +TEST_F(TypeErasedTest, TestSetCommandLineOptionWithMode_SET_FLAGS_VALUE) { + EXPECT_TRUE(flags::SetCommandLineOptionWithMode("int_flag", "101", + flags::SET_FLAGS_VALUE)); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 101); + + EXPECT_TRUE(flags::SetCommandLineOptionWithMode("string_flag", "asdfgh", + flags::SET_FLAGS_VALUE)); + EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), "asdfgh"); + + EXPECT_FALSE(flags::SetCommandLineOptionWithMode("bool_retired_flag", "true", + flags::SET_FLAGS_VALUE)); + + EXPECT_FALSE(flags::SetCommandLineOptionWithMode("unknown_flag", "true", + flags::SET_FLAGS_VALUE)); +} + +// -------------------------------------------------------------------- + +TEST_F(TypeErasedTest, TestSetCommandLineOptionWithMode_SET_FLAG_IF_DEFAULT) { + EXPECT_TRUE(flags::SetCommandLineOptionWithMode("int_flag", "101", + flags::SET_FLAG_IF_DEFAULT)); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 101); + + // This semantic is broken. We return true instead of false. Value is not + // updated. + EXPECT_TRUE(flags::SetCommandLineOptionWithMode("int_flag", "202", + flags::SET_FLAG_IF_DEFAULT)); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 101); + + EXPECT_TRUE(flags::SetCommandLineOptionWithMode("string_flag", "asdfgh", + flags::SET_FLAG_IF_DEFAULT)); + EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), "asdfgh"); + + EXPECT_FALSE(flags::SetCommandLineOptionWithMode("bool_retired_flag", "true", + flags::SET_FLAG_IF_DEFAULT)); + + EXPECT_FALSE(flags::SetCommandLineOptionWithMode("unknown_flag", "true", + flags::SET_FLAG_IF_DEFAULT)); +} + +// -------------------------------------------------------------------- + +TEST_F(TypeErasedTest, TestSetCommandLineOptionWithMode_SET_FLAGS_DEFAULT) { + EXPECT_TRUE(flags::SetCommandLineOptionWithMode("int_flag", "101", + flags::SET_FLAGS_DEFAULT)); + + EXPECT_TRUE(flags::SetCommandLineOptionWithMode("string_flag", "asdfgh", + flags::SET_FLAGS_DEFAULT)); + EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), "asdfgh"); + + EXPECT_FALSE(flags::SetCommandLineOptionWithMode("bool_retired_flag", "true", + flags::SET_FLAGS_DEFAULT)); + + EXPECT_FALSE(flags::SetCommandLineOptionWithMode("unknown_flag", "true", + flags::SET_FLAGS_DEFAULT)); + + // This should be successfull, since flag is still is not set + EXPECT_TRUE(flags::SetCommandLineOptionWithMode("int_flag", "202", + flags::SET_FLAG_IF_DEFAULT)); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 202); +} + +// -------------------------------------------------------------------- + +TEST_F(TypeErasedTest, TestIsValidFlagValue) { + EXPECT_TRUE(flags::IsValidFlagValue("int_flag", "57")); + EXPECT_TRUE(flags::IsValidFlagValue("int_flag", "-101")); + EXPECT_FALSE(flags::IsValidFlagValue("int_flag", "1.1")); + + EXPECT_TRUE(flags::IsValidFlagValue("string_flag", "#%^#%^$%DGHDG$W%adsf")); + + EXPECT_TRUE(flags::IsValidFlagValue("bool_retired_flag", "true")); +} + +} // namespace diff --git a/absl/flags/internal/usage.cc b/absl/flags/internal/usage.cc new file mode 100644 index 000000000000..f17cc6c5787d --- /dev/null +++ b/absl/flags/internal/usage.cc @@ -0,0 +1,392 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/internal/usage.h" + +#include <map> +#include <string> + +#include "absl/flags/flag.h" +#include "absl/flags/internal/path_util.h" +#include "absl/flags/internal/program_name.h" +#include "absl/flags/usage_config.h" +#include "absl/strings/ascii.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "absl/synchronization/mutex.h" + +ABSL_FLAG(bool, help, false, + "show help on important flags for this binary [tip: all flags can " + "have two dashes]"); +ABSL_FLAG(bool, helpfull, false, "show help on all flags"); +ABSL_FLAG(bool, helpshort, false, + "show help on only the main module for this program"); +ABSL_FLAG(bool, helppackage, false, + "show help on all modules in the main package"); +ABSL_FLAG(bool, version, false, "show version and build info and exit"); +ABSL_FLAG(bool, only_check_args, false, "exit after checking all flags"); +ABSL_FLAG(std::string, helpon, "", + "show help on the modules named by this flag value"); +ABSL_FLAG(std::string, helpmatch, "", + "show help on modules whose name contains the specified substr"); + +namespace absl { +namespace flags_internal { +namespace { + +// This class is used to emit an XML element with `tag` and `text`. +// It adds opening and closing tags and escapes special characters in the text. +// For example: +// std::cout << XMLElement("title", "Milk & Cookies"); +// prints "<title>Milk & Cookies</title>" +class XMLElement { + public: + XMLElement(absl::string_view tag, absl::string_view txt) + : tag_(tag), txt_(txt) {} + + friend std::ostream& operator<<(std::ostream& out, + const XMLElement& xml_elem) { + out << "<" << xml_elem.tag_ << ">"; + + for (auto c : xml_elem.txt_) { + switch (c) { + case '"': + out << """; + break; + case '\'': + out << "'"; + break; + case '&': + out << "&"; + break; + case '<': + out << "<"; + break; + case '>': + out << ">"; + break; + default: + out << c; + break; + } + } + + return out << "</" << xml_elem.tag_ << ">"; + } + + private: + absl::string_view tag_; + absl::string_view txt_; +}; + +// -------------------------------------------------------------------- +// Helper class to pretty-print info about a flag. + +class FlagHelpPrettyPrinter { + public: + // Pretty printer holds on to the std::ostream& reference to direct an output + // to that stream. + FlagHelpPrettyPrinter(int max_line_len, std::ostream* out) + : out_(*out), + max_line_len_(max_line_len), + line_len_(0), + first_line_(true) {} + + void Write(absl::string_view str, bool wrap_line = false) { + // Empty std::string - do nothing. + if (str.empty()) return; + + std::vector<absl::string_view> tokens; + if (wrap_line) { + tokens = absl::StrSplit(str, absl::ByAnyChar(" \f\n\r\t\v"), + absl::SkipEmpty()); + } else { + tokens.push_back(str); + } + + for (auto token : tokens) { + bool new_line = (line_len_ == 0); + + // Write the token, ending the std::string first if necessary/possible. + if (!new_line && (line_len_ + token.size() >= max_line_len_)) { + EndLine(); + new_line = true; + } + + if (new_line) { + StartLine(); + } else { + out_ << ' '; + ++line_len_; + } + + out_ << token; + line_len_ += token.size(); + } + } + + void StartLine() { + if (first_line_) { + out_ << " "; + line_len_ = 4; + first_line_ = false; + } else { + out_ << " "; + line_len_ = 6; + } + } + void EndLine() { + out_ << '\n'; + line_len_ = 0; + } + + private: + std::ostream& out_; + const int max_line_len_; + int line_len_; + bool first_line_; +}; + +void FlagHelpHumanReadable(const flags_internal::CommandLineFlag& flag, + std::ostream* out) { + FlagHelpPrettyPrinter printer(80, out); // Max line length is 80. + + // Flag name. + printer.Write(absl::StrCat("-", flag.Name())); + + // Flag help. + printer.Write(absl::StrCat("(", flag.Help(), ");"), /*wrap_line=*/true); + + // Flag data type (for V1 flags only). + if (!flag.IsAbseilFlag() && !flag.IsRetired()) { + printer.Write(absl::StrCat("type: ", flag.Typename(), ";")); + } + + // The listed default value will be the actual default from the flag + // definition in the originating source file, unless the value has + // subsequently been modified using SetCommandLineOption() with mode + // SET_FLAGS_DEFAULT. + std::string dflt_val = flag.DefaultValue(); + if (flag.IsOfType<std::string>()) { + dflt_val = absl::StrCat("\"", dflt_val, "\""); + } + printer.Write(absl::StrCat("default: ", dflt_val, ";")); + + if (flag.modified) { + std::string curr_val = flag.CurrentValue(); + if (flag.IsOfType<std::string>()) { + curr_val = absl::StrCat("\"", curr_val, "\""); + } + printer.Write(absl::StrCat("currently: ", curr_val, ";")); + } + + printer.EndLine(); +} + +// Shows help for every filename which matches any of the filters +// If filters are empty, shows help for every file. +// If a flag's help message has been stripped (e.g. by adding '#define +// STRIP_FLAG_HELP 1' then this flag will not be displayed by '--help' +// and its variants. +void FlagsHelpImpl(std::ostream& out, flags_internal::FlagKindFilter filter_cb, + HelpFormat format = HelpFormat::kHumanReadable) { + if (format == HelpFormat::kHumanReadable) { + out << flags_internal::ShortProgramInvocationName() << ": " + << flags_internal::ProgramUsageMessage() << "\n\n"; + } else { + // XML schema is not a part of our public API for now. + out << "<?xml version=\"1.0\"?>\n" + // The document. + << "<AllFlags>\n" + // The program name and usage. + << XMLElement("program", flags_internal::ShortProgramInvocationName()) + << '\n' + << XMLElement("usage", flags_internal::ProgramUsageMessage()) << '\n'; + } + + // Map of package name to + // map of file name to + // vector of flags in the file. + // This map is used to output matching flags grouped by package and file + // name. + std::map<std::string, + std::map<std::string, + std::vector<const flags_internal::CommandLineFlag*>>> + matching_flags; + + flags_internal::ForEachFlag([&](flags_internal::CommandLineFlag* flag) { + absl::MutexLock l(InitFlagIfNecessary(flag)); + + std::string flag_filename = flag->Filename(); + + // Ignore retired flags. + if (flag->IsRetired()) return; + + // If the flag has been stripped, pretend that it doesn't exist. + if (flag->Help() == flags_internal::kStrippedFlagHelp) return; + + // Make sure flag satisfies the filter + if (!filter_cb || !filter_cb(flag_filename)) return; + + matching_flags[std::string(flags_internal::Package(flag_filename))] + [flag_filename] + .push_back(flag); + }); + + absl::string_view + package_separator; // controls blank lines between packages. + absl::string_view file_separator; // controls blank lines between files. + for (const auto& package : matching_flags) { + if (format == HelpFormat::kHumanReadable) { + out << package_separator; + package_separator = "\n\n"; + } + + file_separator = ""; + for (const auto& flags_in_file : package.second) { + if (format == HelpFormat::kHumanReadable) { + out << file_separator << " Flags from " << flags_in_file.first + << ":\n"; + file_separator = "\n"; + } + + for (const auto* flag : flags_in_file.second) { + flags_internal::FlagHelp(out, *flag, format); + } + } + } + + if (format == HelpFormat::kHumanReadable) { + if (filter_cb && matching_flags.empty()) { + out << " No modules matched: use -helpfull\n"; + } + } else { + // The end of the document. + out << "</AllFlags>\n"; + } +} + +ABSL_CONST_INIT absl::Mutex usage_message_guard(absl::kConstInit); +ABSL_CONST_INIT std::string* program_usage_message + GUARDED_BY(usage_message_guard) = nullptr; + +} // namespace + +// -------------------------------------------------------------------- +// Sets the "usage" message to be used by help reporting routines. + +void SetProgramUsageMessage(absl::string_view new_usage_message) { + absl::MutexLock l(&usage_message_guard); + + if (flags_internal::program_usage_message != nullptr) { + ABSL_INTERNAL_LOG(FATAL, "SetProgramUsageMessage() called twice."); + std::exit(1); + } + + program_usage_message = new std::string(new_usage_message); +} + +// -------------------------------------------------------------------- +// Returns the usage message set by SetProgramUsageMessage(). +// Note: We able to return string_view here only because calling +// SetProgramUsageMessage twice is prohibited. +absl::string_view ProgramUsageMessage() { + absl::MutexLock l(&usage_message_guard); + + return program_usage_message != nullptr + ? absl::string_view(*program_usage_message) + : "Warning: SetProgramUsageMessage() never called"; +} + +// -------------------------------------------------------------------- +// Produces the help message describing specific flag. +void FlagHelp(std::ostream& out, const flags_internal::CommandLineFlag& flag, + HelpFormat format) { + if (format == HelpFormat::kHumanReadable) + flags_internal::FlagHelpHumanReadable(flag, &out); +} + +// -------------------------------------------------------------------- +// Produces the help messages for all flags matching the filter. +// If filter is empty produces help messages for all flags. +void FlagsHelp(std::ostream& out, absl::string_view filter, HelpFormat format) { + flags_internal::FlagKindFilter filter_cb = [&](absl::string_view filename) { + return filter.empty() || filename.find(filter) != absl::string_view::npos; + }; + flags_internal::FlagsHelpImpl(out, filter_cb, format); +} + +// -------------------------------------------------------------------- +// Checks all the 'usage' command line flags to see if any have been set. +// If so, handles them appropriately. +int HandleUsageFlags(std::ostream& out) { + if (absl::GetFlag(FLAGS_helpshort)) { + flags_internal::FlagsHelpImpl( + out, flags_internal::GetUsageConfig().contains_helpshort_flags, + HelpFormat::kHumanReadable); + return 1; + } + + if (absl::GetFlag(FLAGS_helpfull)) { + // show all options + flags_internal::FlagsHelp(out); + return 1; + } + + if (!absl::GetFlag(FLAGS_helpon).empty()) { + flags_internal::FlagsHelp( + out, absl::StrCat("/", absl::GetFlag(FLAGS_helpon), ".")); + return 1; + } + + if (!absl::GetFlag(FLAGS_helpmatch).empty()) { + flags_internal::FlagsHelp(out, absl::GetFlag(FLAGS_helpmatch)); + return 1; + } + + if (absl::GetFlag(FLAGS_help)) { + flags_internal::FlagsHelpImpl( + out, flags_internal::GetUsageConfig().contains_help_flags); + + out << "\nTry --helpfull to get a list of all flags.\n"; + + return 1; + } + + if (absl::GetFlag(FLAGS_helppackage)) { + flags_internal::FlagsHelpImpl( + out, flags_internal::GetUsageConfig().contains_helppackage_flags); + + out << "\nTry --helpfull to get a list of all flags.\n"; + + return 1; + } + + if (absl::GetFlag(FLAGS_version)) { + if (flags_internal::GetUsageConfig().version_string) + out << flags_internal::GetUsageConfig().version_string(); + // Unlike help, we may be asking for version in a script, so return 0 + return 0; + } + + if (absl::GetFlag(FLAGS_only_check_args)) { + return 0; + } + + return -1; +} + +} // namespace flags_internal +} // namespace absl diff --git a/absl/flags/internal/usage.h b/absl/flags/internal/usage.h new file mode 100644 index 000000000000..2d0ea7a72a4d --- /dev/null +++ b/absl/flags/internal/usage.h @@ -0,0 +1,91 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_FLAGS_INTERNAL_USAGE_H_ +#define ABSL_FLAGS_INTERNAL_USAGE_H_ + +#include <iosfwd> +#include <string> + +#include "absl/flags/declare.h" +#include "absl/flags/internal/commandlineflag.h" +#include "absl/strings/string_view.h" + +// -------------------------------------------------------------------- +// Usage reporting interfaces + +namespace absl { +namespace flags_internal { + +// Sets the "usage" message to be used by help reporting routines. +// For example: +// absl::SetProgramUsageMessage( +// absl::StrCat("This program does nothing. Sample usage:\n", argv[0], +// " <uselessarg1> <uselessarg2>")); +// Do not include commandline flags in the usage: we do that for you! +// Note: Calling SetProgramUsageMessage twice will trigger a call to std::exit. +void SetProgramUsageMessage(absl::string_view new_usage_message); + +// Returns the usage message set by SetProgramUsageMessage(). +absl::string_view ProgramUsageMessage(); + +// -------------------------------------------------------------------- + +// The format to report the help messages in. +enum class HelpFormat { + kHumanReadable, +}; + +// Outputs the help message describing specific flag. +void FlagHelp(std::ostream& out, const flags_internal::CommandLineFlag& flag, + HelpFormat format = HelpFormat::kHumanReadable); + +// Produces the help messages for all flags matching the filter. A flag matches +// the filter if it is defined in a file with a filename which includes +// filter string as a substring. You can use '/' and '.' to restrict the +// matching to a specific file names. For example: +// FlagsHelp(out, "/path/to/file."); +// restricts help to only flags which resides in files named like: +// .../path/to/file.<ext> +// for any extension 'ext'. If the filter is empty this function produces help +// messages for all flags. +void FlagsHelp(std::ostream& out, absl::string_view filter = {}, + HelpFormat format = HelpFormat::kHumanReadable); + +// -------------------------------------------------------------------- + +// If any of the 'usage' related command line flags (listed on the bottom of +// this file) has been set this routine produces corresponding help message in +// the specified output stream and returns: +// 0 - if "version" or "only_check_flags" flags were set and handled. +// 1 - if some other 'usage' related flag was set and handled. +// -1 - if no usage flags were set on a commmand line. +// Non negative return values are expected to be used as an exit code for a +// binary. +int HandleUsageFlags(std::ostream& out); + +} // namespace flags_internal +} // namespace absl + +ABSL_DECLARE_FLAG(bool, help); +ABSL_DECLARE_FLAG(bool, helpfull); +ABSL_DECLARE_FLAG(bool, helpshort); +ABSL_DECLARE_FLAG(bool, helppackage); +ABSL_DECLARE_FLAG(bool, version); +ABSL_DECLARE_FLAG(bool, only_check_args); +ABSL_DECLARE_FLAG(std::string, helpon); +ABSL_DECLARE_FLAG(std::string, helpmatch); + +#endif // ABSL_FLAGS_INTERNAL_USAGE_H_ diff --git a/absl/flags/internal/usage_test.cc b/absl/flags/internal/usage_test.cc new file mode 100644 index 000000000000..cb40aa42378d --- /dev/null +++ b/absl/flags/internal/usage_test.cc @@ -0,0 +1,367 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <sstream> + +#include "gtest/gtest.h" +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "absl/flags/internal/path_util.h" +#include "absl/flags/internal/program_name.h" +#include "absl/flags/internal/usage.h" +#include "absl/flags/usage_config.h" +#include "absl/memory/memory.h" +#include "absl/strings/match.h" + +ABSL_FLAG(int, usage_reporting_test_flag_01, 101, + "usage_reporting_test_flag_01 help message"); +ABSL_FLAG(bool, usage_reporting_test_flag_02, false, + "usage_reporting_test_flag_02 help message"); +ABSL_FLAG(double, usage_reporting_test_flag_03, 1.03, + "usage_reporting_test_flag_03 help message"); +ABSL_FLAG(int64_t, usage_reporting_test_flag_04, 1000000000000004L, + "usage_reporting_test_flag_04 help message"); + +struct UDT { + UDT() = default; + UDT(const UDT&) = default; +}; +bool AbslParseFlag(absl::string_view, UDT*, std::string*) { return true; } +std::string AbslUnparseFlag(const UDT&) { return "UDT{}"; } + +ABSL_FLAG(UDT, usage_reporting_test_flag_05, {}, + "usage_reporting_test_flag_05 help message"); + +namespace { + +namespace flags = absl::flags_internal; + +static std::string NormalizeFileName(absl::string_view fname) { +#ifdef _WIN32 + std::string normalized(fname); + std::replace(normalized.begin(), normalized.end(), '\\', '/'); + fname = normalized; +#endif + + auto absl_pos = fname.find("/absl/"); + if (absl_pos != absl::string_view::npos) { + fname = fname.substr(absl_pos + 1); + } + return std::string(fname); +} + +class UsageReportingTest : public testing::Test { + protected: + UsageReportingTest() { + // Install default config for the use on this unit test. + // Binary may install a custom config before tests are run. + absl::FlagsUsageConfig default_config; + default_config.normalize_filename = &NormalizeFileName; + absl::SetFlagsUsageConfig(default_config); + } + + private: + flags::FlagSaver flag_saver_; +}; + +// -------------------------------------------------------------------- + +using UsageReportingDeathTest = UsageReportingTest; + +TEST_F(UsageReportingDeathTest, TestSetProgramUsageMessage) { + EXPECT_EQ(flags::ProgramUsageMessage(), "Custom usage message"); + +#ifndef _WIN32 + // TODO(rogeeff): figure out why this does not work on Windows. + EXPECT_DEATH(flags::SetProgramUsageMessage("custom usage message"), + ".*SetProgramUsageMessage\\(\\) called twice.*"); +#endif +} + +// -------------------------------------------------------------------- + +TEST_F(UsageReportingTest, TestFlagHelpHRF_on_flag_01) { + const auto* flag = flags::FindCommandLineFlag("usage_reporting_test_flag_01"); + std::stringstream test_buf; + + flags::FlagHelp(test_buf, *flag, flags::HelpFormat::kHumanReadable); + EXPECT_EQ( + test_buf.str(), + R"( -usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); + default: 101; +)"); +} + +TEST_F(UsageReportingTest, TestFlagHelpHRF_on_flag_02) { + const auto* flag = flags::FindCommandLineFlag("usage_reporting_test_flag_02"); + std::stringstream test_buf; + + flags::FlagHelp(test_buf, *flag, flags::HelpFormat::kHumanReadable); + EXPECT_EQ( + test_buf.str(), + R"( -usage_reporting_test_flag_02 (usage_reporting_test_flag_02 help message); + default: false; +)"); +} + +TEST_F(UsageReportingTest, TestFlagHelpHRF_on_flag_03) { + const auto* flag = flags::FindCommandLineFlag("usage_reporting_test_flag_03"); + std::stringstream test_buf; + + flags::FlagHelp(test_buf, *flag, flags::HelpFormat::kHumanReadable); + EXPECT_EQ( + test_buf.str(), + R"( -usage_reporting_test_flag_03 (usage_reporting_test_flag_03 help message); + default: 1.03; +)"); +} + +TEST_F(UsageReportingTest, TestFlagHelpHRF_on_flag_04) { + const auto* flag = flags::FindCommandLineFlag("usage_reporting_test_flag_04"); + std::stringstream test_buf; + + flags::FlagHelp(test_buf, *flag, flags::HelpFormat::kHumanReadable); + EXPECT_EQ( + test_buf.str(), + R"( -usage_reporting_test_flag_04 (usage_reporting_test_flag_04 help message); + default: 1000000000000004; +)"); +} + +TEST_F(UsageReportingTest, TestFlagHelpHRF_on_flag_05) { + const auto* flag = flags::FindCommandLineFlag("usage_reporting_test_flag_05"); + std::stringstream test_buf; + + flags::FlagHelp(test_buf, *flag, flags::HelpFormat::kHumanReadable); + EXPECT_EQ( + test_buf.str(), + R"( -usage_reporting_test_flag_05 (usage_reporting_test_flag_05 help message); + default: UDT{}; +)"); +} + +// -------------------------------------------------------------------- + +TEST_F(UsageReportingTest, TestFlagsHelpHRF) { + std::string usage_test_flags_out = + R"(usage_test: Custom usage message + + Flags from absl/flags/internal/usage_test.cc: + -usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); + default: 101; + -usage_reporting_test_flag_02 (usage_reporting_test_flag_02 help message); + default: false; + -usage_reporting_test_flag_03 (usage_reporting_test_flag_03 help message); + default: 1.03; + -usage_reporting_test_flag_04 (usage_reporting_test_flag_04 help message); + default: 1000000000000004; + -usage_reporting_test_flag_05 (usage_reporting_test_flag_05 help message); + default: UDT{}; +)"; + + std::stringstream test_buf_01; + flags::FlagsHelp(test_buf_01, "usage_test.cc", + flags::HelpFormat::kHumanReadable); + EXPECT_EQ(test_buf_01.str(), usage_test_flags_out); + + std::stringstream test_buf_02; + flags::FlagsHelp(test_buf_02, "flags/internal/usage_test.cc", + flags::HelpFormat::kHumanReadable); + EXPECT_EQ(test_buf_02.str(), usage_test_flags_out); + + std::stringstream test_buf_03; + flags::FlagsHelp(test_buf_03, "usage_test", + flags::HelpFormat::kHumanReadable); + EXPECT_EQ(test_buf_03.str(), usage_test_flags_out); + + std::stringstream test_buf_04; + flags::FlagsHelp(test_buf_04, "flags/invalid_file_name.cc", + flags::HelpFormat::kHumanReadable); + EXPECT_EQ(test_buf_04.str(), + R"(usage_test: Custom usage message + + No modules matched: use -helpfull +)"); + + std::stringstream test_buf_05; + flags::FlagsHelp(test_buf_05, "", flags::HelpFormat::kHumanReadable); + std::string test_out = test_buf_05.str(); + absl::string_view test_out_str(test_out); + EXPECT_TRUE( + absl::StartsWith(test_out_str, "usage_test: Custom usage message")); + EXPECT_TRUE(absl::StrContains( + test_out_str, "Flags from absl/flags/internal/usage_test.cc:")); + EXPECT_TRUE(absl::StrContains(test_out_str, + "Flags from absl/flags/internal/usage.cc:")); + EXPECT_TRUE( + absl::StrContains(test_out_str, "-usage_reporting_test_flag_01 ")); + EXPECT_TRUE(absl::StrContains(test_out_str, "-help (show help")) + << test_out_str; +} + +// -------------------------------------------------------------------- + +TEST_F(UsageReportingTest, TestNoUsageFlags) { + std::stringstream test_buf; + EXPECT_EQ(flags::HandleUsageFlags(test_buf), -1); +} + +// -------------------------------------------------------------------- + +TEST_F(UsageReportingTest, TestUsageFlag_helpshort) { + absl::SetFlag(&FLAGS_helpshort, true); + + std::stringstream test_buf; + EXPECT_EQ(flags::HandleUsageFlags(test_buf), 1); + EXPECT_EQ(test_buf.str(), + R"(usage_test: Custom usage message + + Flags from absl/flags/internal/usage_test.cc: + -usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); + default: 101; + -usage_reporting_test_flag_02 (usage_reporting_test_flag_02 help message); + default: false; + -usage_reporting_test_flag_03 (usage_reporting_test_flag_03 help message); + default: 1.03; + -usage_reporting_test_flag_04 (usage_reporting_test_flag_04 help message); + default: 1000000000000004; + -usage_reporting_test_flag_05 (usage_reporting_test_flag_05 help message); + default: UDT{}; +)"); +} + +// -------------------------------------------------------------------- + +TEST_F(UsageReportingTest, TestUsageFlag_help) { + absl::SetFlag(&FLAGS_help, true); + + std::stringstream test_buf; + EXPECT_EQ(flags::HandleUsageFlags(test_buf), 1); + EXPECT_EQ(test_buf.str(), + R"(usage_test: Custom usage message + + Flags from absl/flags/internal/usage_test.cc: + -usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); + default: 101; + -usage_reporting_test_flag_02 (usage_reporting_test_flag_02 help message); + default: false; + -usage_reporting_test_flag_03 (usage_reporting_test_flag_03 help message); + default: 1.03; + -usage_reporting_test_flag_04 (usage_reporting_test_flag_04 help message); + default: 1000000000000004; + -usage_reporting_test_flag_05 (usage_reporting_test_flag_05 help message); + default: UDT{}; + +Try --helpfull to get a list of all flags. +)"); +} + +// -------------------------------------------------------------------- + +TEST_F(UsageReportingTest, TestUsageFlag_helppackage) { + absl::SetFlag(&FLAGS_helppackage, true); + + std::stringstream test_buf; + EXPECT_EQ(flags::HandleUsageFlags(test_buf), 1); + EXPECT_EQ(test_buf.str(), + R"(usage_test: Custom usage message + + Flags from absl/flags/internal/usage_test.cc: + -usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); + default: 101; + -usage_reporting_test_flag_02 (usage_reporting_test_flag_02 help message); + default: false; + -usage_reporting_test_flag_03 (usage_reporting_test_flag_03 help message); + default: 1.03; + -usage_reporting_test_flag_04 (usage_reporting_test_flag_04 help message); + default: 1000000000000004; + -usage_reporting_test_flag_05 (usage_reporting_test_flag_05 help message); + default: UDT{}; + +Try --helpfull to get a list of all flags. +)"); +} + +// -------------------------------------------------------------------- + +TEST_F(UsageReportingTest, TestUsageFlag_version) { + absl::SetFlag(&FLAGS_version, true); + + std::stringstream test_buf; + EXPECT_EQ(flags::HandleUsageFlags(test_buf), 0); +#ifndef NDEBUG + EXPECT_EQ(test_buf.str(), + "usage_test\nDebug build (NDEBUG not #defined)\n"); +#else + EXPECT_EQ(test_buf.str(), "usage_test\n"); +#endif +} + +// -------------------------------------------------------------------- + +TEST_F(UsageReportingTest, TestUsageFlag_only_check_args) { + absl::SetFlag(&FLAGS_only_check_args, true); + + std::stringstream test_buf; + EXPECT_EQ(flags::HandleUsageFlags(test_buf), 0); + EXPECT_EQ(test_buf.str(), ""); +} + +// -------------------------------------------------------------------- + +TEST_F(UsageReportingTest, TestUsageFlag_helpon) { + absl::SetFlag(&FLAGS_helpon, "bla-bla"); + + std::stringstream test_buf_01; + EXPECT_EQ(flags::HandleUsageFlags(test_buf_01), 1); + EXPECT_EQ(test_buf_01.str(), + R"(usage_test: Custom usage message + + No modules matched: use -helpfull +)"); + + absl::SetFlag(&FLAGS_helpon, "usage_test"); + + std::stringstream test_buf_02; + EXPECT_EQ(flags::HandleUsageFlags(test_buf_02), 1); + EXPECT_EQ(test_buf_02.str(), + R"(usage_test: Custom usage message + + Flags from absl/flags/internal/usage_test.cc: + -usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); + default: 101; + -usage_reporting_test_flag_02 (usage_reporting_test_flag_02 help message); + default: false; + -usage_reporting_test_flag_03 (usage_reporting_test_flag_03 help message); + default: 1.03; + -usage_reporting_test_flag_04 (usage_reporting_test_flag_04 help message); + default: 1000000000000004; + -usage_reporting_test_flag_05 (usage_reporting_test_flag_05 help message); + default: UDT{}; +)"); +} + +// -------------------------------------------------------------------- + +} // namespace + +int main(int argc, char* argv[]) { + absl::GetFlag(FLAGS_undefok); // Force linking of parse.cc + flags::SetProgramInvocationName("usage_test"); + flags::SetProgramUsageMessage("Custom usage message"); + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/absl/flags/marshalling.cc b/absl/flags/marshalling.cc new file mode 100644 index 000000000000..f025ae7e86c4 --- /dev/null +++ b/absl/flags/marshalling.cc @@ -0,0 +1,189 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/marshalling.h" + +#include <limits> + +#include "absl/base/macros.h" +#include "absl/strings/match.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "absl/strings/str_split.h" + +namespace absl { +namespace flags_internal { + +// -------------------------------------------------------------------- +// AbslParseFlag specializations for boolean type. + +bool AbslParseFlag(absl::string_view text, bool* dst, std::string*) { + const char* kTrue[] = {"1", "t", "true", "y", "yes"}; + const char* kFalse[] = {"0", "f", "false", "n", "no"}; + static_assert(sizeof(kTrue) == sizeof(kFalse), "true_false_equal"); + + text = absl::StripAsciiWhitespace(text); + + for (size_t i = 0; i < ABSL_ARRAYSIZE(kTrue); ++i) { + if (absl::EqualsIgnoreCase(text, kTrue[i])) { + *dst = true; + return true; + } else if (absl::EqualsIgnoreCase(text, kFalse[i])) { + *dst = false; + return true; + } + } + return false; // didn't match a legal input +} + +// -------------------------------------------------------------------- +// AbslParseFlag for integral types. + +// Return the base to use for parsing text as an integer. Leading 0x +// puts us in base 16. But leading 0 does not put us in base 8. It +// caused too many bugs when we had that behavior. +static int NumericBase(absl::string_view text) { + const bool hex = (text.size() >= 2 && text[0] == '0' && + (text[1] == 'x' || text[1] == 'X')); + return hex ? 16 : 10; +} + +template <typename IntType> +inline bool ParseFlagImpl(absl::string_view text, IntType* dst) { + text = absl::StripAsciiWhitespace(text); + + return absl::numbers_internal::safe_strtoi_base(text, dst, NumericBase(text)); +} + +bool AbslParseFlag(absl::string_view text, short* dst, std::string*) { + int val; + if (!ParseFlagImpl(text, &val)) return false; + if (static_cast<short>(val) != val) // worked, but number out of range + return false; + *dst = static_cast<short>(val); + return true; +} + +bool AbslParseFlag(absl::string_view text, unsigned short* dst, std::string*) { + unsigned int val; + if (!ParseFlagImpl(text, &val)) return false; + if (static_cast<unsigned short>(val) != + val) // worked, but number out of range + return false; + *dst = static_cast<unsigned short>(val); + return true; +} + +bool AbslParseFlag(absl::string_view text, int* dst, std::string*) { + return ParseFlagImpl(text, dst); +} + +bool AbslParseFlag(absl::string_view text, unsigned int* dst, std::string*) { + return ParseFlagImpl(text, dst); +} + +bool AbslParseFlag(absl::string_view text, long* dst, std::string*) { + return ParseFlagImpl(text, dst); +} + +bool AbslParseFlag(absl::string_view text, unsigned long* dst, std::string*) { + return ParseFlagImpl(text, dst); +} + +bool AbslParseFlag(absl::string_view text, long long* dst, std::string*) { + return ParseFlagImpl(text, dst); +} + +bool AbslParseFlag(absl::string_view text, unsigned long long* dst, + std::string*) { + return ParseFlagImpl(text, dst); +} + +// -------------------------------------------------------------------- +// AbslParseFlag for floating point types. + +bool AbslParseFlag(absl::string_view text, float* dst, std::string*) { + return absl::SimpleAtof(text, dst); +} + +bool AbslParseFlag(absl::string_view text, double* dst, std::string*) { + return absl::SimpleAtod(text, dst); +} + +// -------------------------------------------------------------------- +// AbslParseFlag for strings. + +bool AbslParseFlag(absl::string_view text, std::string* dst, std::string*) { + dst->assign(text.data(), text.size()); + return true; +} + +// -------------------------------------------------------------------- +// AbslParseFlag for vector of strings. + +bool AbslParseFlag(absl::string_view text, std::vector<std::string>* dst, + std::string*) { + // An empty flag value corresponds to an empty vector, not a vector + // with a single, empty std::string. + if (text.empty()) { + dst->clear(); + return true; + } + *dst = absl::StrSplit(text, ',', absl::AllowEmpty()); + return true; +} + +// -------------------------------------------------------------------- +// AbslUnparseFlag specializations for various builtin flag types. + +std::string Unparse(bool v) { return v ? "true" : "false"; } +std::string Unparse(short v) { return absl::StrCat(v); } +std::string Unparse(unsigned short v) { return absl::StrCat(v); } +std::string Unparse(int v) { return absl::StrCat(v); } +std::string Unparse(unsigned int v) { return absl::StrCat(v); } +std::string Unparse(long v) { return absl::StrCat(v); } +std::string Unparse(unsigned long v) { return absl::StrCat(v); } +std::string Unparse(long long v) { return absl::StrCat(v); } +std::string Unparse(unsigned long long v) { return absl::StrCat(v); } +template <typename T> +std::string UnparseFloatingPointVal(T v) { + // digits10 is guaranteed to roundtrip correctly in std::string -> value -> std::string + // conversions, but may not be enough to represent all the values correctly. + std::string digit10_str = + absl::StrFormat("%.*g", std::numeric_limits<T>::digits10, v); + if (std::isnan(v) || std::isinf(v)) return digit10_str; + + T roundtrip_val = 0; + std::string err; + if (absl::ParseFlag(digit10_str, &roundtrip_val, &err) && + roundtrip_val == v) { + return digit10_str; + } + + // max_digits10 is the number of base-10 digits that are necessary to uniquely + // represent all distinct values. + return absl::StrFormat("%.*g", std::numeric_limits<T>::max_digits10, v); +} +std::string Unparse(float v) { return UnparseFloatingPointVal(v); } +std::string Unparse(double v) { return UnparseFloatingPointVal(v); } +std::string AbslUnparseFlag(absl::string_view v) { return std::string(v); } +std::string AbslUnparseFlag(const std::vector<std::string>& v) { + return absl::StrJoin(v, ","); +} + +} // namespace flags_internal +} // namespace absl diff --git a/absl/flags/marshalling.h b/absl/flags/marshalling.h new file mode 100644 index 000000000000..669cf452c4b0 --- /dev/null +++ b/absl/flags/marshalling.h @@ -0,0 +1,260 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: marshalling.h +// ----------------------------------------------------------------------------- +// +// This header file defines the API for extending Abseil flag support to +// custom types, and defines the set of overloads for fundamental types. +// +// Out of the box, the Abseil flags library supports the following types: +// +// * `bool` +// * `int16_t` +// * `uint16_t` +// * `int32_t` +// * `uint32_t` +// * `int64_t` +// * `uint64_t` +// * `float` +// * `double` +// * `std::string` +// * `std::vector<std::string>` +// +// Note that support for integral types is implemented using overloads for +// variable-width fundamental types (`short`, `int`, `long`, etc.). However, +// you should prefer the fixed-width integral types (`int32_t`, `uint64_t`, +// etc.) we've noted above within flag definitions. + +// +// In addition, several Abseil libraries provide their own custom support for +// Abseil flags. +// +// The Abseil time library provides the following support for civil time values: +// +// * `absl::CivilSecond` +// * `absl::CivilMinute` +// * `absl::CivilHour` +// * `absl::CivilDay` +// * `absl::CivilMonth` +// * `absl::CivilYear` +// +// and also provides support for the following absolute time values: +// +// * `absl::Duration` +// * `absl::Time` +// +// Additional support for Abseil types will be noted here as it is added. +// +// You can also provide your own custom flags by adding overloads for +// `AbslParseFlag()` and `AbslUnparseFlag()` to your type definitions. (See +// below.) +// +// ----------------------------------------------------------------------------- +// Adding Type Support for Abseil Flags +// ----------------------------------------------------------------------------- +// +// To add support for your user-defined type, add overloads of `AbslParseFlag()` +// and `AbslUnparseFlag()` as free (non-member) functions to your type. If `T` +// is a class type, these functions can be friend function definitions. These +// overloads must be added to the same namespace where the type is defined, so +// that they can be discovered by Argument-Dependent Lookup (ADL). +// +// Example: +// +// namespace foo { +// +// enum OutputMode { kPlainText, kHtml }; +// +// // AbslParseFlag converts from a string to OutputMode. +// // Must be in same namespace as OutputMode. +// +// // Parses an OutputMode from the command line flag value `text. Returns +// // `true` and sets `*mode` on success; returns `false` and sets `*error` +// // on failure. +// bool AbslParseFlag(absl::string_view text, +// OutputMode* mode, +// std::string* error) { +// if (text == "plaintext") { +// *mode = kPlainText; +// return true; +// } +// if (text == "html") { +// *mode = kHtml; +// return true; +// } +// *error = "unknown value for enumeration"; +// return false; +// } +// +// // AbslUnparseFlag converts from an OutputMode to a string. +// // Must be in same namespace as OutputMode. +// +// // Returns a textual flag value corresponding to the OutputMode `mode`. +// std::string AbslUnparseFlag(OutputMode mode) { +// switch (mode) { +// case kPlainText: return "plaintext"; +// case kHtml: return "html"; +// default: return SimpleItoa(mode); +// } +// } +// +// Notice that neither `AbslParseFlag()` nor `AbslUnparseFlag()` are class +// members, but free functions. `AbslParseFlag/AbslUnparseFlag()` overloads +// for a type should only be declared in the same file and namespace as said +// type. The proper `AbslParseFlag/AbslUnparseFlag()` implementations for a +// given type will be discovered via Argument-Dependent Lookup (ADL). +// +// `AbslParseFlag()` may need, in turn, to parse simpler constituent types +// using `absl::ParseFlag()`. For example, a custom struct `MyFlagType` +// consisting of a `std::pair<int, std::string>` would add an `AbslParseFlag()` +// overload for its `MyFlagType` like so: +// +// Example: +// +// namespace my_flag_type { +// +// struct MyFlagType { +// std::pair<int, std::string> my_flag_data; +// }; +// +// bool AbslParseFlag(absl::string_view text, MyFlagType* flag, +// std::string* err); +// +// std::string AbslUnparseFlag(const MyFlagType&); +// +// // Within the implementation, `AbslParseFlag()` will, in turn invoke +// // `absl::ParseFlag()` on its constituent `int` and `std::string` types +// // (which have built-in Abseil flag support. +// +// bool AbslParseFlag(absl::string_view text, MyFlagType* flag, +// std::string* err) { +// std::pair<absl::string_view, absl::string_view> tokens = +// absl::StrSplit(text, ','); +// if (!absl::ParseFlag(tokens.first, &flag->my_flag_data.first, err)) +// return false; +// if (!absl::ParseFlag(tokens.second, &flag->my_flag_data.second, err)) +// return false; +// return true; +// } +// +// // Similarly, for unparsing, we can simply invoke `absl::UnparseFlag()` on +// // the constituent types. +// std::string AbslUnparseFlag(const MyFlagType& flag) { +// return absl::StrCat(absl::UnparseFlag(flag.my_flag_data.first), +// ",", +// absl::UnparseFlag(flag.my_flag_data.second)); +// } +#ifndef ABSL_FLAGS_MARSHALLING_H_ +#define ABSL_FLAGS_MARSHALLING_H_ + +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" + +namespace absl { +namespace flags_internal { + +// Overloads of `AbslParseFlag()` and `AbslUnparseFlag()` for fundamental types. +bool AbslParseFlag(absl::string_view, bool*, std::string*); +bool AbslParseFlag(absl::string_view, short*, std::string*); // NOLINT +bool AbslParseFlag(absl::string_view, unsigned short*, std::string*); // NOLINT +bool AbslParseFlag(absl::string_view, int*, std::string*); // NOLINT +bool AbslParseFlag(absl::string_view, unsigned int*, std::string*); // NOLINT +bool AbslParseFlag(absl::string_view, long*, std::string*); // NOLINT +bool AbslParseFlag(absl::string_view, unsigned long*, std::string*); // NOLINT +bool AbslParseFlag(absl::string_view, long long*, std::string*); // NOLINT +bool AbslParseFlag(absl::string_view, unsigned long long*, + std::string*); // NOLINT +bool AbslParseFlag(absl::string_view, float*, std::string*); +bool AbslParseFlag(absl::string_view, double*, std::string*); +bool AbslParseFlag(absl::string_view, std::string*, std::string*); +bool AbslParseFlag(absl::string_view, std::vector<std::string>*, std::string*); + +struct GlobalStringADLGuard { + explicit GlobalStringADLGuard(std::string* p) : ptr(p) {} + operator std::string*() { return ptr; } // NOLINT + std::string* ptr; +}; + +template <typename T> +bool InvokeParseFlag(absl::string_view input, T* dst, std::string* err) { + // Comment on next line provides a good compiler error message if T + // does not have AbslParseFlag(absl::string_view, T*, std::string*). + return AbslParseFlag( // Is T missing AbslParseFlag? + input, dst, GlobalStringADLGuard(err)); +} + +// Strings and std:: containers do not have the same overload resolution +// considerations as fundamental types. Naming these 'AbslUnparseFlag' means we +// can avoid the need for additional specializations of Unparse (below). +std::string AbslUnparseFlag(absl::string_view v); +std::string AbslUnparseFlag(const std::vector<std::string>&); + +template <typename T> +std::string Unparse(const T& v) { + // Comment on next line provides a good compiler error message if T does not + // have UnparseFlag. + return AbslUnparseFlag(v); // Is T missing AbslUnparseFlag? +} + +// Overloads for builtin types. +std::string Unparse(bool v); +std::string Unparse(short v); // NOLINT +std::string Unparse(unsigned short v); // NOLINT +std::string Unparse(int v); // NOLINT +std::string Unparse(unsigned int v); // NOLINT +std::string Unparse(long v); // NOLINT +std::string Unparse(unsigned long v); // NOLINT +std::string Unparse(long long v); // NOLINT +std::string Unparse(unsigned long long v); // NOLINT +std::string Unparse(float v); +std::string Unparse(double v); + +} // namespace flags_internal + +// ParseFlag() +// +// Parses a string value into a flag value of type `T`. Do not add overloads of +// this function for your type directly; instead, add an `AbslParseFlag()` +// free function as documented above. +// +// Some implementations of `AbslParseFlag()` for types which consist of other, +// constituent types which already have Abseil flag support, may need to call +// `absl::ParseFlag()` on those consituent string values. (See above.) +template <typename T> +inline bool ParseFlag(absl::string_view input, T* dst, std::string* error) { + return flags_internal::InvokeParseFlag(input, dst, error); +} + +// UnparseFlag() +// +// Unparses a flag value of type `T` into a string value. Do not add overloads +// of this function for your type directly; instead, add an `AbslUnparseFlag()` +// free function as documented above. +// +// Some implementations of `AbslUnparseFlag()` for types which consist of other, +// constituent types which already have Abseil flag support, may want to call +// `absl::UnparseFlag()` on those constituent types. (See above.) +template <typename T> +inline std::string UnparseFlag(const T& v) { + return flags_internal::Unparse(v); +} + +} // namespace absl + +#endif // ABSL_FLAGS_MARSHALLING_H_ diff --git a/absl/flags/marshalling_test.cc b/absl/flags/marshalling_test.cc new file mode 100644 index 000000000000..37cd1940e34d --- /dev/null +++ b/absl/flags/marshalling_test.cc @@ -0,0 +1,899 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/marshalling.h" + +#include <cmath> + +#include "gtest/gtest.h" + +namespace { + +TEST(MarshallingTest, TestBoolParsing) { + std::string err; + bool value; + + // True values. + EXPECT_TRUE(absl::ParseFlag("True", &value, &err)); + EXPECT_TRUE(value); + EXPECT_TRUE(absl::ParseFlag("true", &value, &err)); + EXPECT_TRUE(value); + EXPECT_TRUE(absl::ParseFlag("TRUE", &value, &err)); + EXPECT_TRUE(value); + + EXPECT_TRUE(absl::ParseFlag("Yes", &value, &err)); + EXPECT_TRUE(value); + EXPECT_TRUE(absl::ParseFlag("yes", &value, &err)); + EXPECT_TRUE(value); + EXPECT_TRUE(absl::ParseFlag("YES", &value, &err)); + EXPECT_TRUE(value); + + EXPECT_TRUE(absl::ParseFlag("t", &value, &err)); + EXPECT_TRUE(value); + EXPECT_TRUE(absl::ParseFlag("T", &value, &err)); + EXPECT_TRUE(value); + + EXPECT_TRUE(absl::ParseFlag("y", &value, &err)); + EXPECT_TRUE(value); + EXPECT_TRUE(absl::ParseFlag("Y", &value, &err)); + EXPECT_TRUE(value); + + EXPECT_TRUE(absl::ParseFlag("1", &value, &err)); + EXPECT_TRUE(value); + + // False values. + EXPECT_TRUE(absl::ParseFlag("False", &value, &err)); + EXPECT_FALSE(value); + EXPECT_TRUE(absl::ParseFlag("false", &value, &err)); + EXPECT_FALSE(value); + EXPECT_TRUE(absl::ParseFlag("FALSE", &value, &err)); + EXPECT_FALSE(value); + + EXPECT_TRUE(absl::ParseFlag("No", &value, &err)); + EXPECT_FALSE(value); + EXPECT_TRUE(absl::ParseFlag("no", &value, &err)); + EXPECT_FALSE(value); + EXPECT_TRUE(absl::ParseFlag("NO", &value, &err)); + EXPECT_FALSE(value); + + EXPECT_TRUE(absl::ParseFlag("f", &value, &err)); + EXPECT_FALSE(value); + EXPECT_TRUE(absl::ParseFlag("F", &value, &err)); + EXPECT_FALSE(value); + + EXPECT_TRUE(absl::ParseFlag("n", &value, &err)); + EXPECT_FALSE(value); + EXPECT_TRUE(absl::ParseFlag("N", &value, &err)); + EXPECT_FALSE(value); + + EXPECT_TRUE(absl::ParseFlag("0", &value, &err)); + EXPECT_FALSE(value); + + // Whitespace handling. + EXPECT_TRUE(absl::ParseFlag(" true", &value, &err)); + EXPECT_TRUE(value); + EXPECT_TRUE(absl::ParseFlag("true ", &value, &err)); + EXPECT_TRUE(value); + EXPECT_TRUE(absl::ParseFlag(" true ", &value, &err)); + EXPECT_TRUE(value); + + // Invalid input. + EXPECT_FALSE(absl::ParseFlag("", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\n", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\t", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("2", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("11", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("tt", &value, &err)); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestInt16Parsing) { + std::string err; + int16_t value; + + // Decimal values. + EXPECT_TRUE(absl::ParseFlag("1", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("0", &value, &err)); + EXPECT_EQ(value, 0); + EXPECT_TRUE(absl::ParseFlag("-1", &value, &err)); + EXPECT_EQ(value, -1); + EXPECT_TRUE(absl::ParseFlag("123", &value, &err)); + EXPECT_EQ(value, 123); + EXPECT_TRUE(absl::ParseFlag("-18765", &value, &err)); + EXPECT_EQ(value, -18765); + EXPECT_TRUE(absl::ParseFlag("+3", &value, &err)); + EXPECT_EQ(value, 3); + + // Leading zero values. + EXPECT_TRUE(absl::ParseFlag("01", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("-001", &value, &err)); + EXPECT_EQ(value, -1); + EXPECT_TRUE(absl::ParseFlag("0000100", &value, &err)); + EXPECT_EQ(value, 100); + + // Hex values. + EXPECT_TRUE(absl::ParseFlag("0x10", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag("0X234", &value, &err)); + EXPECT_EQ(value, 564); + // TODO(rogeeff): fix below validations + EXPECT_FALSE(absl::ParseFlag("-0x7FFD", &value, &err)); + EXPECT_NE(value, -3); + EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_NE(value, 49); + + // Whitespace handling + EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); + EXPECT_EQ(value, 10); + EXPECT_TRUE(absl::ParseFlag(" 11", &value, &err)); + EXPECT_EQ(value, 11); + EXPECT_TRUE(absl::ParseFlag(" 012 ", &value, &err)); + EXPECT_EQ(value, 12); + EXPECT_TRUE(absl::ParseFlag(" 0x22 ", &value, &err)); + EXPECT_EQ(value, 34); + + // Invalid values. + EXPECT_FALSE(absl::ParseFlag("", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("40000", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("--1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\n", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\t", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("2U", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("FFF", &value, &err)); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestUint16Parsing) { + std::string err; + uint16_t value; + + // Decimal values. + EXPECT_TRUE(absl::ParseFlag("1", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("0", &value, &err)); + EXPECT_EQ(value, 0); + EXPECT_TRUE(absl::ParseFlag("123", &value, &err)); + EXPECT_EQ(value, 123); + EXPECT_TRUE(absl::ParseFlag("+3", &value, &err)); + EXPECT_EQ(value, 3); + + // Leading zero values. + EXPECT_TRUE(absl::ParseFlag("01", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("001", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("0000100", &value, &err)); + EXPECT_EQ(value, 100); + + // Hex values. + EXPECT_TRUE(absl::ParseFlag("0x10", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag("0X234", &value, &err)); + EXPECT_EQ(value, 564); + // TODO(rogeeff): fix below validations + EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_NE(value, 49); + + // Whitespace handling + EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); + EXPECT_EQ(value, 10); + EXPECT_TRUE(absl::ParseFlag(" 11", &value, &err)); + EXPECT_EQ(value, 11); + EXPECT_TRUE(absl::ParseFlag(" 012 ", &value, &err)); + EXPECT_EQ(value, 12); + EXPECT_TRUE(absl::ParseFlag(" 0x22 ", &value, &err)); + EXPECT_EQ(value, 34); + + // Invalid values. + EXPECT_FALSE(absl::ParseFlag("", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("70000", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("-1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("--1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\n", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\t", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("2U", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("FFF", &value, &err)); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestInt32Parsing) { + std::string err; + int32_t value; + + // Decimal values. + EXPECT_TRUE(absl::ParseFlag("1", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("0", &value, &err)); + EXPECT_EQ(value, 0); + EXPECT_TRUE(absl::ParseFlag("-1", &value, &err)); + EXPECT_EQ(value, -1); + EXPECT_TRUE(absl::ParseFlag("123", &value, &err)); + EXPECT_EQ(value, 123); + EXPECT_TRUE(absl::ParseFlag("-98765", &value, &err)); + EXPECT_EQ(value, -98765); + EXPECT_TRUE(absl::ParseFlag("+3", &value, &err)); + EXPECT_EQ(value, 3); + + // Leading zero values. + EXPECT_TRUE(absl::ParseFlag("01", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("-001", &value, &err)); + EXPECT_EQ(value, -1); + EXPECT_TRUE(absl::ParseFlag("0000100", &value, &err)); + EXPECT_EQ(value, 100); + + // Hex values. + EXPECT_TRUE(absl::ParseFlag("0x10", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag("0X234", &value, &err)); + EXPECT_EQ(value, 564); + // TODO(rogeeff): fix below validations + EXPECT_FALSE(absl::ParseFlag("-0x7FFFFFFD", &value, &err)); + EXPECT_NE(value, -3); + EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_NE(value, 49); + + // Whitespace handling + EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); + EXPECT_EQ(value, 10); + EXPECT_TRUE(absl::ParseFlag(" 11", &value, &err)); + EXPECT_EQ(value, 11); + EXPECT_TRUE(absl::ParseFlag(" 012 ", &value, &err)); + EXPECT_EQ(value, 12); + EXPECT_TRUE(absl::ParseFlag(" 0x22 ", &value, &err)); + EXPECT_EQ(value, 34); + + // Invalid values. + EXPECT_FALSE(absl::ParseFlag("", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("70000000000", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("--1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\n", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\t", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("2U", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("FFF", &value, &err)); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestUint32Parsing) { + std::string err; + uint32_t value; + + // Decimal values. + EXPECT_TRUE(absl::ParseFlag("1", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("0", &value, &err)); + EXPECT_EQ(value, 0); + EXPECT_TRUE(absl::ParseFlag("123", &value, &err)); + EXPECT_EQ(value, 123); + EXPECT_TRUE(absl::ParseFlag("+3", &value, &err)); + EXPECT_EQ(value, 3); + + // Leading zero values. + EXPECT_TRUE(absl::ParseFlag("01", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("0000100", &value, &err)); + EXPECT_EQ(value, 100); + + // Hex values. + EXPECT_TRUE(absl::ParseFlag("0x10", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag("0X234", &value, &err)); + EXPECT_EQ(value, 564); + EXPECT_TRUE(absl::ParseFlag("0xFFFFFFFD", &value, &err)); + EXPECT_EQ(value, 4294967293); + // TODO(rogeeff): fix below validations + EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_NE(value, 49); + + // Whitespace handling + EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); + EXPECT_EQ(value, 10); + EXPECT_TRUE(absl::ParseFlag(" 11", &value, &err)); + EXPECT_EQ(value, 11); + EXPECT_TRUE(absl::ParseFlag(" 012 ", &value, &err)); + EXPECT_EQ(value, 12); + EXPECT_TRUE(absl::ParseFlag(" 0x22 ", &value, &err)); + EXPECT_EQ(value, 34); + + // Invalid values. + EXPECT_FALSE(absl::ParseFlag("", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("140000000000", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("-1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("--1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\n", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\t", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("2U", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("FFF", &value, &err)); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestInt64Parsing) { + std::string err; + int64_t value; + + // Decimal values. + EXPECT_TRUE(absl::ParseFlag("1", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("0", &value, &err)); + EXPECT_EQ(value, 0); + EXPECT_TRUE(absl::ParseFlag("-1", &value, &err)); + EXPECT_EQ(value, -1); + EXPECT_TRUE(absl::ParseFlag("123", &value, &err)); + EXPECT_EQ(value, 123); + EXPECT_TRUE(absl::ParseFlag("-98765", &value, &err)); + EXPECT_EQ(value, -98765); + EXPECT_TRUE(absl::ParseFlag("+3", &value, &err)); + EXPECT_EQ(value, 3); + + // Leading zero values. + EXPECT_TRUE(absl::ParseFlag("01", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("001", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("0000100", &value, &err)); + EXPECT_EQ(value, 100); + + // Hex values. + EXPECT_TRUE(absl::ParseFlag("0x10", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag("0XFFFAAABBBCCCDDD", &value, &err)); + EXPECT_EQ(value, 1152827684197027293); + // TODO(rogeeff): fix below validation + EXPECT_FALSE(absl::ParseFlag("-0x7FFFFFFFFFFFFFFE", &value, &err)); + EXPECT_NE(value, -2); + EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_NE(value, 49); + + // Whitespace handling + EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); + EXPECT_EQ(value, 10); + EXPECT_TRUE(absl::ParseFlag(" 11", &value, &err)); + EXPECT_EQ(value, 11); + EXPECT_TRUE(absl::ParseFlag(" 012 ", &value, &err)); + EXPECT_EQ(value, 12); + EXPECT_TRUE(absl::ParseFlag(" 0x7F ", &value, &err)); + EXPECT_EQ(value, 127); + + // Invalid values. + EXPECT_FALSE(absl::ParseFlag("", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("0xFFFFFFFFFFFFFFFFFF", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("--1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\n", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\t", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("2U", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("FFF", &value, &err)); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestUInt64Parsing) { + std::string err; + uint64_t value; + + // Decimal values. + EXPECT_TRUE(absl::ParseFlag("1", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("0", &value, &err)); + EXPECT_EQ(value, 0); + EXPECT_TRUE(absl::ParseFlag("123", &value, &err)); + EXPECT_EQ(value, 123); + EXPECT_TRUE(absl::ParseFlag("+13", &value, &err)); + EXPECT_EQ(value, 13); + + // Leading zero values. + EXPECT_TRUE(absl::ParseFlag("01", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("001", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("0000300", &value, &err)); + EXPECT_EQ(value, 300); + + // Hex values. + EXPECT_TRUE(absl::ParseFlag("0x10", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag("0XFFFF", &value, &err)); + EXPECT_EQ(value, 65535); + // TODO(rogeeff): fix below validation + EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_NE(value, 49); + + // Whitespace handling + EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); + EXPECT_EQ(value, 10); + EXPECT_TRUE(absl::ParseFlag(" 11", &value, &err)); + EXPECT_EQ(value, 11); + EXPECT_TRUE(absl::ParseFlag(" 012 ", &value, &err)); + EXPECT_EQ(value, 12); + + // Invalid values. + EXPECT_FALSE(absl::ParseFlag("", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("0xFFFFFFFFFFFFFFFFFF", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("-1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("--1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\n", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\t", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("2U", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("FFF", &value, &err)); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestFloatParsing) { + std::string err; + float value; + + // Ordinary values. + EXPECT_TRUE(absl::ParseFlag("1.3", &value, &err)); + EXPECT_FLOAT_EQ(value, 1.3f); + EXPECT_TRUE(absl::ParseFlag("-0.1", &value, &err)); + EXPECT_DOUBLE_EQ(value, -0.1f); + EXPECT_TRUE(absl::ParseFlag("+0.01", &value, &err)); + EXPECT_DOUBLE_EQ(value, 0.01f); + + // Scientific values. + EXPECT_TRUE(absl::ParseFlag("1.2e3", &value, &err)); + EXPECT_DOUBLE_EQ(value, 1.2e3f); + EXPECT_TRUE(absl::ParseFlag("9.8765402e-37", &value, &err)); + EXPECT_DOUBLE_EQ(value, 9.8765402e-37f); + EXPECT_TRUE(absl::ParseFlag("0.11e+3", &value, &err)); + EXPECT_DOUBLE_EQ(value, 0.11e+3f); + EXPECT_TRUE(absl::ParseFlag("1.e-2300", &value, &err)); + EXPECT_DOUBLE_EQ(value, 0.f); + EXPECT_TRUE(absl::ParseFlag("1.e+2300", &value, &err)); + EXPECT_TRUE(std::isinf(value)); + + // Leading zero values. + EXPECT_TRUE(absl::ParseFlag("01.6", &value, &err)); + EXPECT_DOUBLE_EQ(value, 1.6f); + EXPECT_TRUE(absl::ParseFlag("000.0001", &value, &err)); + EXPECT_DOUBLE_EQ(value, 0.0001f); + + // Trailing zero values. + EXPECT_TRUE(absl::ParseFlag("-5.1000", &value, &err)); + EXPECT_DOUBLE_EQ(value, -5.1f); + + // Exceptional values. + EXPECT_TRUE(absl::ParseFlag("NaN", &value, &err)); + EXPECT_TRUE(std::isnan(value)); + EXPECT_TRUE(absl::ParseFlag("Inf", &value, &err)); + EXPECT_TRUE(std::isinf(value)); + + // Hex values + EXPECT_TRUE(absl::ParseFlag("0x10.23p12", &value, &err)); + EXPECT_DOUBLE_EQ(value, 66096.f); + EXPECT_TRUE(absl::ParseFlag("-0xF1.A3p-2", &value, &err)); + EXPECT_NEAR(value, -60.4092f, 5e-5f); + EXPECT_TRUE(absl::ParseFlag("+0x0.0AAp-12", &value, &err)); + EXPECT_NEAR(value, 1.01328e-05f, 5e-11f); + EXPECT_TRUE(absl::ParseFlag("0x.01p1", &value, &err)); + EXPECT_NEAR(value, 0.0078125f, 5e-8f); + + // Whitespace handling + EXPECT_TRUE(absl::ParseFlag("10.1 ", &value, &err)); + EXPECT_DOUBLE_EQ(value, 10.1f); + EXPECT_TRUE(absl::ParseFlag(" 2.34", &value, &err)); + EXPECT_DOUBLE_EQ(value, 2.34f); + EXPECT_TRUE(absl::ParseFlag(" 5.7 ", &value, &err)); + EXPECT_DOUBLE_EQ(value, 5.7f); + EXPECT_TRUE(absl::ParseFlag(" -0xE0.F3p01 ", &value, &err)); + EXPECT_NEAR(value, -449.8984375f, 5e-8f); + + // Invalid values. + EXPECT_FALSE(absl::ParseFlag("", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("--1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\n", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\t", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("2.3xxx", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("0x0.1pAA", &value, &err)); + // TODO(rogeeff): below assertion should fail + EXPECT_TRUE(absl::ParseFlag("0x0.1", &value, &err)); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestDoubleParsing) { + std::string err; + double value; + + // Ordinary values. + EXPECT_TRUE(absl::ParseFlag("1.3", &value, &err)); + EXPECT_DOUBLE_EQ(value, 1.3); + EXPECT_TRUE(absl::ParseFlag("-0.1", &value, &err)); + EXPECT_DOUBLE_EQ(value, -0.1); + EXPECT_TRUE(absl::ParseFlag("+0.01", &value, &err)); + EXPECT_DOUBLE_EQ(value, 0.01); + + // Scientific values. + EXPECT_TRUE(absl::ParseFlag("1.2e3", &value, &err)); + EXPECT_DOUBLE_EQ(value, 1.2e3); + EXPECT_TRUE(absl::ParseFlag("9.00000002e-123", &value, &err)); + EXPECT_DOUBLE_EQ(value, 9.00000002e-123); + EXPECT_TRUE(absl::ParseFlag("0.11e+3", &value, &err)); + EXPECT_DOUBLE_EQ(value, 0.11e+3); + EXPECT_TRUE(absl::ParseFlag("1.e-2300", &value, &err)); + EXPECT_DOUBLE_EQ(value, 0); + EXPECT_TRUE(absl::ParseFlag("1.e+2300", &value, &err)); + EXPECT_TRUE(std::isinf(value)); + + // Leading zero values. + EXPECT_TRUE(absl::ParseFlag("01.6", &value, &err)); + EXPECT_DOUBLE_EQ(value, 1.6); + EXPECT_TRUE(absl::ParseFlag("000.0001", &value, &err)); + EXPECT_DOUBLE_EQ(value, 0.0001); + + // Trailing zero values. + EXPECT_TRUE(absl::ParseFlag("-5.1000", &value, &err)); + EXPECT_DOUBLE_EQ(value, -5.1); + + // Exceptional values. + EXPECT_TRUE(absl::ParseFlag("NaN", &value, &err)); + EXPECT_TRUE(std::isnan(value)); + EXPECT_TRUE(absl::ParseFlag("nan", &value, &err)); + EXPECT_TRUE(std::isnan(value)); + EXPECT_TRUE(absl::ParseFlag("Inf", &value, &err)); + EXPECT_TRUE(std::isinf(value)); + EXPECT_TRUE(absl::ParseFlag("inf", &value, &err)); + EXPECT_TRUE(std::isinf(value)); + + // Hex values + EXPECT_TRUE(absl::ParseFlag("0x10.23p12", &value, &err)); + EXPECT_DOUBLE_EQ(value, 66096); + EXPECT_TRUE(absl::ParseFlag("-0xF1.A3p-2", &value, &err)); + EXPECT_NEAR(value, -60.4092, 5e-5); + EXPECT_TRUE(absl::ParseFlag("+0x0.0AAp-12", &value, &err)); + EXPECT_NEAR(value, 1.01328e-05, 5e-11); + EXPECT_TRUE(absl::ParseFlag("0x.01p1", &value, &err)); + EXPECT_NEAR(value, 0.0078125, 5e-8); + + // Whitespace handling + EXPECT_TRUE(absl::ParseFlag("10.1 ", &value, &err)); + EXPECT_DOUBLE_EQ(value, 10.1); + EXPECT_TRUE(absl::ParseFlag(" 2.34", &value, &err)); + EXPECT_DOUBLE_EQ(value, 2.34); + EXPECT_TRUE(absl::ParseFlag(" 5.7 ", &value, &err)); + EXPECT_DOUBLE_EQ(value, 5.7); + EXPECT_TRUE(absl::ParseFlag(" -0xE0.F3p01 ", &value, &err)); + EXPECT_NEAR(value, -449.8984375, 5e-8); + + // Invalid values. + EXPECT_FALSE(absl::ParseFlag("", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("--1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\n", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\t", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("2.3xxx", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("0x0.1pAA", &value, &err)); + // TODO(rogeeff): below assertion should fail + EXPECT_TRUE(absl::ParseFlag("0x0.1", &value, &err)); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestStringParsing) { + std::string err; + std::string value; + + EXPECT_TRUE(absl::ParseFlag("", &value, &err)); + EXPECT_EQ(value, ""); + EXPECT_TRUE(absl::ParseFlag(" ", &value, &err)); + EXPECT_EQ(value, " "); + EXPECT_TRUE(absl::ParseFlag(" ", &value, &err)); + EXPECT_EQ(value, " "); + EXPECT_TRUE(absl::ParseFlag("\n", &value, &err)); + EXPECT_EQ(value, "\n"); + EXPECT_TRUE(absl::ParseFlag("\t", &value, &err)); + EXPECT_EQ(value, "\t"); + EXPECT_TRUE(absl::ParseFlag("asdfg", &value, &err)); + EXPECT_EQ(value, "asdfg"); + EXPECT_TRUE(absl::ParseFlag("asdf ghjk", &value, &err)); + EXPECT_EQ(value, "asdf ghjk"); + EXPECT_TRUE(absl::ParseFlag("a\nb\nc", &value, &err)); + EXPECT_EQ(value, "a\nb\nc"); + EXPECT_TRUE(absl::ParseFlag("asd\0fgh", &value, &err)); + EXPECT_EQ(value, "asd"); + EXPECT_TRUE(absl::ParseFlag("\\\\", &value, &err)); + EXPECT_EQ(value, "\\\\"); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestVectorOfStringParsing) { + std::string err; + std::vector<std::string> value; + + EXPECT_TRUE(absl::ParseFlag("", &value, &err)); + EXPECT_EQ(value, std::vector<std::string>{}); + EXPECT_TRUE(absl::ParseFlag("1", &value, &err)); + EXPECT_EQ(value, std::vector<std::string>({"1"})); + EXPECT_TRUE(absl::ParseFlag("a,b", &value, &err)); + EXPECT_EQ(value, std::vector<std::string>({"a", "b"})); + EXPECT_TRUE(absl::ParseFlag("a,b,c,", &value, &err)); + EXPECT_EQ(value, std::vector<std::string>({"a", "b", "c", ""})); + EXPECT_TRUE(absl::ParseFlag("a,,", &value, &err)); + EXPECT_EQ(value, std::vector<std::string>({"a", "", ""})); + EXPECT_TRUE(absl::ParseFlag(",", &value, &err)); + EXPECT_EQ(value, std::vector<std::string>({"", ""})); + EXPECT_TRUE(absl::ParseFlag("a, b,c ", &value, &err)); + EXPECT_EQ(value, std::vector<std::string>({"a", " b", "c "})); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestBoolUnparsing) { + EXPECT_EQ(absl::UnparseFlag(true), "true"); + EXPECT_EQ(absl::UnparseFlag(false), "false"); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestInt16Unparsing) { + int16_t value; + + value = 1; + EXPECT_EQ(absl::UnparseFlag(value), "1"); + value = 0; + EXPECT_EQ(absl::UnparseFlag(value), "0"); + value = -1; + EXPECT_EQ(absl::UnparseFlag(value), "-1"); + value = 9876; + EXPECT_EQ(absl::UnparseFlag(value), "9876"); + value = -987; + EXPECT_EQ(absl::UnparseFlag(value), "-987"); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestUint16Unparsing) { + uint16_t value; + + value = 1; + EXPECT_EQ(absl::UnparseFlag(value), "1"); + value = 0; + EXPECT_EQ(absl::UnparseFlag(value), "0"); + value = 19876; + EXPECT_EQ(absl::UnparseFlag(value), "19876"); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestInt32Unparsing) { + int32_t value; + + value = 1; + EXPECT_EQ(absl::UnparseFlag(value), "1"); + value = 0; + EXPECT_EQ(absl::UnparseFlag(value), "0"); + value = -1; + EXPECT_EQ(absl::UnparseFlag(value), "-1"); + value = 12345; + EXPECT_EQ(absl::UnparseFlag(value), "12345"); + value = -987; + EXPECT_EQ(absl::UnparseFlag(value), "-987"); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestUint32Unparsing) { + uint32_t value; + + value = 1; + EXPECT_EQ(absl::UnparseFlag(value), "1"); + value = 0; + EXPECT_EQ(absl::UnparseFlag(value), "0"); + value = 1234500; + EXPECT_EQ(absl::UnparseFlag(value), "1234500"); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestInt64Unparsing) { + int64_t value; + + value = 1; + EXPECT_EQ(absl::UnparseFlag(value), "1"); + value = 0; + EXPECT_EQ(absl::UnparseFlag(value), "0"); + value = -1; + EXPECT_EQ(absl::UnparseFlag(value), "-1"); + value = 123456789L; + EXPECT_EQ(absl::UnparseFlag(value), "123456789"); + value = -987654321L; + EXPECT_EQ(absl::UnparseFlag(value), "-987654321"); + value = 0x7FFFFFFFFFFFFFFF; + EXPECT_EQ(absl::UnparseFlag(value), "9223372036854775807"); + value = 0xFFFFFFFFFFFFFFFF; + EXPECT_EQ(absl::UnparseFlag(value), "-1"); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestUint64Unparsing) { + uint64_t value; + + value = 1; + EXPECT_EQ(absl::UnparseFlag(value), "1"); + value = 0; + EXPECT_EQ(absl::UnparseFlag(value), "0"); + value = 123456789L; + EXPECT_EQ(absl::UnparseFlag(value), "123456789"); + value = 0xFFFFFFFFFFFFFFFF; + EXPECT_EQ(absl::UnparseFlag(value), "18446744073709551615"); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestFloatUnparsing) { + float value; + + value = 1.1f; + EXPECT_EQ(absl::UnparseFlag(value), "1.1"); + value = 0.01f; + EXPECT_EQ(absl::UnparseFlag(value), "0.01"); + value = 1.23e-2f; + EXPECT_EQ(absl::UnparseFlag(value), "0.0123"); + value = -0.71f; + EXPECT_EQ(absl::UnparseFlag(value), "-0.71"); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestDoubleUnparsing) { + double value; + + value = 1.1; + EXPECT_EQ(absl::UnparseFlag(value), "1.1"); + value = 0.01; + EXPECT_EQ(absl::UnparseFlag(value), "0.01"); + value = 1.23e-2; + EXPECT_EQ(absl::UnparseFlag(value), "0.0123"); + value = -0.71; + EXPECT_EQ(absl::UnparseFlag(value), "-0.71"); + value = -0; + EXPECT_EQ(absl::UnparseFlag(value), "0"); + value = std::nan(""); + EXPECT_EQ(absl::UnparseFlag(value), "nan"); + value = std::numeric_limits<double>::infinity(); + EXPECT_EQ(absl::UnparseFlag(value), "inf"); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestStringUnparsing) { + EXPECT_EQ(absl::UnparseFlag(""), ""); + EXPECT_EQ(absl::UnparseFlag(" "), " "); + EXPECT_EQ(absl::UnparseFlag("qwerty"), "qwerty"); + EXPECT_EQ(absl::UnparseFlag("ASDFGH"), "ASDFGH"); + EXPECT_EQ(absl::UnparseFlag("\n\t "), "\n\t "); +} + +// -------------------------------------------------------------------- + +template <typename T> +void TestRoundtrip(T v) { + T new_v; + std::string err; + EXPECT_TRUE(absl::ParseFlag(absl::UnparseFlag(v), &new_v, &err)); + EXPECT_EQ(new_v, v); +} + +TEST(MarshallingTest, TestFloatRoundTrip) { + TestRoundtrip(0.1f); + TestRoundtrip(0.12f); + TestRoundtrip(0.123f); + TestRoundtrip(0.1234f); + TestRoundtrip(0.12345f); + TestRoundtrip(0.123456f); + TestRoundtrip(0.1234567f); + TestRoundtrip(0.12345678f); + + TestRoundtrip(0.1e20f); + TestRoundtrip(0.12e20f); + TestRoundtrip(0.123e20f); + TestRoundtrip(0.1234e20f); + TestRoundtrip(0.12345e20f); + TestRoundtrip(0.123456e20f); + TestRoundtrip(0.1234567e20f); + TestRoundtrip(0.12345678e20f); + + TestRoundtrip(0.1e-20f); + TestRoundtrip(0.12e-20f); + TestRoundtrip(0.123e-20f); + TestRoundtrip(0.1234e-20f); + TestRoundtrip(0.12345e-20f); + TestRoundtrip(0.123456e-20f); + TestRoundtrip(0.1234567e-20f); + TestRoundtrip(0.12345678e-20f); +} + +TEST(MarshallingTest, TestDoubleRoundTrip) { + TestRoundtrip(0.1); + TestRoundtrip(0.12); + TestRoundtrip(0.123); + TestRoundtrip(0.1234); + TestRoundtrip(0.12345); + TestRoundtrip(0.123456); + TestRoundtrip(0.1234567); + TestRoundtrip(0.12345678); + TestRoundtrip(0.123456789); + TestRoundtrip(0.1234567891); + TestRoundtrip(0.12345678912); + TestRoundtrip(0.123456789123); + TestRoundtrip(0.1234567891234); + TestRoundtrip(0.12345678912345); + TestRoundtrip(0.123456789123456); + TestRoundtrip(0.1234567891234567); + TestRoundtrip(0.12345678912345678); + + TestRoundtrip(0.1e50); + TestRoundtrip(0.12e50); + TestRoundtrip(0.123e50); + TestRoundtrip(0.1234e50); + TestRoundtrip(0.12345e50); + TestRoundtrip(0.123456e50); + TestRoundtrip(0.1234567e50); + TestRoundtrip(0.12345678e50); + TestRoundtrip(0.123456789e50); + TestRoundtrip(0.1234567891e50); + TestRoundtrip(0.12345678912e50); + TestRoundtrip(0.123456789123e50); + TestRoundtrip(0.1234567891234e50); + TestRoundtrip(0.12345678912345e50); + TestRoundtrip(0.123456789123456e50); + TestRoundtrip(0.1234567891234567e50); + TestRoundtrip(0.12345678912345678e50); + + TestRoundtrip(0.1e-50); + TestRoundtrip(0.12e-50); + TestRoundtrip(0.123e-50); + TestRoundtrip(0.1234e-50); + TestRoundtrip(0.12345e-50); + TestRoundtrip(0.123456e-50); + TestRoundtrip(0.1234567e-50); + TestRoundtrip(0.12345678e-50); + TestRoundtrip(0.123456789e-50); + TestRoundtrip(0.1234567891e-50); + TestRoundtrip(0.12345678912e-50); + TestRoundtrip(0.123456789123e-50); + TestRoundtrip(0.1234567891234e-50); + TestRoundtrip(0.12345678912345e-50); + TestRoundtrip(0.123456789123456e-50); + TestRoundtrip(0.1234567891234567e-50); + TestRoundtrip(0.12345678912345678e-50); +} + +} // namespace diff --git a/absl/flags/parse.cc b/absl/flags/parse.cc new file mode 100644 index 000000000000..b19619602433 --- /dev/null +++ b/absl/flags/parse.cc @@ -0,0 +1,762 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/parse.h" + +#include <stdlib.h> +#include <fstream> +#include <iostream> +#include <tuple> + +#ifdef _WIN32 +#include <windows.h> +#endif + +#include "absl/flags/flag.h" +#include "absl/flags/internal/program_name.h" +#include "absl/flags/internal/registry.h" +#include "absl/flags/internal/usage.h" +#include "absl/flags/usage_config.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/strip.h" +#include "absl/synchronization/mutex.h" + +// -------------------------------------------------------------------- + +namespace absl { +namespace flags_internal { +namespace { + +ABSL_CONST_INIT absl::Mutex processing_checks_guard(absl::kConstInit); + +ABSL_CONST_INIT bool flagfile_needs_processing + GUARDED_BY(processing_checks_guard) = false; +ABSL_CONST_INIT bool fromenv_needs_processing + GUARDED_BY(processing_checks_guard) = false; +ABSL_CONST_INIT bool tryfromenv_needs_processing + GUARDED_BY(processing_checks_guard) = false; + +} // namespace +} // namespace flags_internal +} // namespace absl + +ABSL_FLAG(std::vector<std::string>, flagfile, {}, + "comma-separated list of files to load flags from") + .OnUpdate([]() { + if (absl::GetFlag(FLAGS_flagfile).empty()) return; + + absl::MutexLock l(&absl::flags_internal::processing_checks_guard); + + // Setting this flag twice before it is handled most likely an internal + // error and should be reviewed by developers. + if (absl::flags_internal::flagfile_needs_processing) { + ABSL_INTERNAL_LOG(WARNING, "flagfile set twice before it is handled"); + } + + absl::flags_internal::flagfile_needs_processing = true; + }); +ABSL_FLAG(std::vector<std::string>, fromenv, {}, + "comma-separated list of flags to set from the environment" + " [use 'export FLAGS_flag1=value']") + .OnUpdate([]() { + if (absl::GetFlag(FLAGS_fromenv).empty()) return; + + absl::MutexLock l(&absl::flags_internal::processing_checks_guard); + + // Setting this flag twice before it is handled most likely an internal + // error and should be reviewed by developers. + if (absl::flags_internal::fromenv_needs_processing) { + ABSL_INTERNAL_LOG(WARNING, "fromenv set twice before it is handled."); + } + + absl::flags_internal::fromenv_needs_processing = true; + }); +ABSL_FLAG(std::vector<std::string>, tryfromenv, {}, + "comma-separated list of flags to try to set from the environment if " + "present") + .OnUpdate([]() { + if (absl::GetFlag(FLAGS_tryfromenv).empty()) return; + + absl::MutexLock l(&absl::flags_internal::processing_checks_guard); + + // Setting this flag twice before it is handled most likely an internal + // error and should be reviewed by developers. + if (absl::flags_internal::tryfromenv_needs_processing) { + ABSL_INTERNAL_LOG(WARNING, + "tryfromenv set twice before it is handled."); + } + + absl::flags_internal::tryfromenv_needs_processing = true; + }); + +ABSL_FLAG(std::vector<std::string>, undefok, {}, + "comma-separated list of flag names that it is okay to specify " + "on the command line even if the program does not define a flag " + "with that name"); + +namespace absl { +namespace flags_internal { + +namespace { + +class ArgsList { + public: + ArgsList() : next_arg_(0) {} + ArgsList(int argc, char* argv[]) : args_(argv, argv + argc), next_arg_(0) {} + explicit ArgsList(const std::vector<std::string>& args) + : args_(args), next_arg_(0) {} + + // Returns success status: true if parsing successful, false otherwise. + bool ReadFromFlagfile(const std::string& flag_file_name); + + int Size() const { return args_.size() - next_arg_; } + int FrontIndex() const { return next_arg_; } + absl::string_view Front() const { return args_[next_arg_]; } + void PopFront() { next_arg_++; } + + private: + std::vector<std::string> args_; + int next_arg_; +}; + +bool ArgsList::ReadFromFlagfile(const std::string& flag_file_name) { + std::ifstream flag_file(flag_file_name); + + if (!flag_file) { + flags_internal::ReportUsageError( + absl::StrCat("Can't open flagfile ", flag_file_name), true); + + return false; + } + + // This argument represents fake argv[0], which should be present in all arg + // lists. + args_.push_back(""); + + std::string line; + bool success = true; + + while (std::getline(flag_file, line)) { + absl::string_view stripped = absl::StripLeadingAsciiWhitespace(line); + + if (stripped.empty() || stripped[0] == '#') { + // Comment or empty line; just ignore. + continue; + } + + if (stripped[0] == '-') { + if (stripped == "--") { + flags_internal::ReportUsageError( + "Flagfile can't contain position arguments or --", true); + + success = false; + break; + } + + args_.push_back(std::string(stripped)); + continue; + } + + flags_internal::ReportUsageError( + absl::StrCat("Unexpected line in the flagfile ", flag_file_name, ": ", + line), + true); + + success = false; + } + + return success; +} + +// -------------------------------------------------------------------- + +// Reads the environment variable with name `name` and stores results in +// `value`. If variable is not present in environment returns false, otherwise +// returns true. +bool GetEnvVar(const char* var_name, std::string* var_value) { +#ifdef _WIN32 + char buf[1024]; + auto get_res = GetEnvironmentVariableA(var_name, buf, sizeof(buf)); + if (get_res >= sizeof(buf)) { + return ""; + } + + if (get_res == 0) { + return false; + } + + *var_value = std::string(buf, get_res); +#else + const char* val = ::getenv(var_name); + if (val == nullptr) { + return false; + } + + *var_value = val; +#endif + + return true; +} + +// -------------------------------------------------------------------- + +// Returns: +// Flag name or empty if arg= -- +// Flag value after = in --flag=value (empty if --foo) +// "Is empty value" status. True if arg= --foo=, false otherwise. This is +// required to separate --foo from --foo=. +// For example: +// arg return values +// "--foo=bar" -> {"foo", "bar", false}. +// "--foo" -> {"foo", "", false}. +// "--foo=" -> {"foo", "", true}. +std::tuple<absl::string_view, absl::string_view, bool> SplitNameAndValue( + absl::string_view arg) { + // Allow -foo and --foo + absl::ConsumePrefix(&arg, "-"); + + if (arg.empty()) { + return std::make_tuple("", "", false); + } + + auto equal_sign_pos = arg.find("="); + + absl::string_view flag_name = arg.substr(0, equal_sign_pos); + + absl::string_view value; + bool is_empty_value = false; + + if (equal_sign_pos != absl::string_view::npos) { + value = arg.substr(equal_sign_pos + 1); + is_empty_value = value.empty(); + } + + return std::make_tuple(flag_name, value, is_empty_value); +} + +// -------------------------------------------------------------------- + +// Returns: +// found flag or nullptr +// is negative in case of --nofoo +std::tuple<CommandLineFlag*, bool> LocateFlag(absl::string_view flag_name) { + CommandLineFlag* flag = flags_internal::FindCommandLineFlag(flag_name); + bool is_negative = false; + + if (!flag && absl::ConsumePrefix(&flag_name, "no")) { + flag = flags_internal::FindCommandLineFlag(flag_name); + is_negative = true; + } + + return std::make_tuple(flag, is_negative); +} + +// -------------------------------------------------------------------- + +// Verify that default values of typed flags must be convertible to string and +// back. +void CheckDefaultValuesParsingRoundtrip() { +#ifndef NDEBUG + flags_internal::ForEachFlag([&](CommandLineFlag* flag) { + if (flag->IsRetired()) return; + +#define IGNORE_TYPE(T) \ + if (flag->IsOfType<T>()) return; + + ABSL_FLAGS_INTERNAL_FOR_EACH_LOCK_FREE(IGNORE_TYPE) + IGNORE_TYPE(std::string) + IGNORE_TYPE(std::vector<std::string>) +#undef IGNORE_TYPE + + absl::MutexLock lock(InitFlagIfNecessary(flag)); + + std::string v = flag->DefaultValue(); + void* dst = Clone(flag->op, flag->def); + std::string error; + if (!flags_internal::Parse(flag->marshalling_op, v, dst, &error)) { + ABSL_INTERNAL_LOG( + FATAL, + absl::StrCat("Flag ", flag->Name(), " (from ", flag->Filename(), + "): std::string form of default value '", v, + "' could not be parsed; error=", error)); + } + + // We do not compare dst to def since parsing/unparsing may make + // small changes, e.g., precision loss for floating point types. + Delete(flag->op, dst); + }); +#endif +} + +// -------------------------------------------------------------------- + +// Returns success status, which is true if we successfully read all flag files, +// in which case new ArgLists are appended to the input_args in a reverse order +// of file names in the input flagfiles list. This order ensures that flags from +// the first flagfile in the input list are processed before the second flagfile +// etc. +bool ReadFlagfiles(const std::vector<std::string>& flagfiles, + std::vector<ArgsList>* input_args) { + bool success = true; + for (auto it = flagfiles.rbegin(); it != flagfiles.rend(); ++it) { + ArgsList al; + + if (al.ReadFromFlagfile(*it)) { + input_args->push_back(al); + } else { + success = false; + } + } + + return success; +} + +// Returns success status, which is true if were able to locate all environment +// variables correctly or if fail_on_absent_in_env is false. The environment +// variable names are expected to be of the form `FLAGS_<flag_name>`, where +// `flag_name` is a string from the input flag_names list. If successful we +// append a single ArgList at the end of the input_args. +bool ReadFlagsFromEnv(const std::vector<std::string>& flag_names, + std::vector<ArgsList>* input_args, + bool fail_on_absent_in_env) { + bool success = true; + std::vector<std::string> args; + + // This argument represents fake argv[0], which should be present in all arg + // lists. + args.push_back(""); + + for (const auto& flag_name : flag_names) { + // Avoid infinite recursion. + if (flag_name == "fromenv" || flag_name == "tryfromenv") { + flags_internal::ReportUsageError( + absl::StrCat("Infinite recursion on flag ", flag_name), true); + + success = false; + continue; + } + + const std::string envname = absl::StrCat("FLAGS_", flag_name); + std::string envval; + if (!GetEnvVar(envname.c_str(), &envval)) { + if (fail_on_absent_in_env) { + flags_internal::ReportUsageError( + absl::StrCat(envname, " not found in environment"), true); + + success = false; + } + + continue; + } + + args.push_back(absl::StrCat("--", flag_name, "=", envval)); + } + + if (success) { + input_args->emplace_back(args); + } + + return success; +} + +// -------------------------------------------------------------------- + +// Returns success status, which is true if were able to handle all generator +// flags (flagfile, fromenv, tryfromemv) successfully. +bool HandleGeneratorFlags(std::vector<ArgsList>* input_args, + std::vector<std::string>* flagfile_value) { + bool success = true; + + absl::MutexLock l(&flags_internal::processing_checks_guard); + + // flagfile could have been set either on a command line or + // programmatically before invoking ParseCommandLine. Note that we do not + // actually process arguments specified in the flagfile, but instead + // create a secondary arguments list to be processed along with the rest + // of the comamnd line arguments. Since we always the process most recently + // created list of arguments first, this will result in flagfile argument + // being processed before any other argument in the command line. If + // FLAGS_flagfile contains more than one file name we create multiple new + // levels of arguments in a reverse order of file names. Thus we always + // process arguments from first file before arguments containing in a + // second file, etc. If flagfile contains another + // --flagfile inside of it, it will produce new level of arguments and + // processed before the rest of the flagfile. We are also collecting all + // flagfiles set on original command line. Unlike the rest of the flags, + // this flag can be set multiple times and is expected to be handled + // multiple times. We are collecting them all into a single list and set + // the value of FLAGS_flagfile to that value at the end of the parsing. + if (flags_internal::flagfile_needs_processing) { + auto flagfiles = absl::GetFlag(FLAGS_flagfile); + + if (input_args->size() == 1) { + flagfile_value->insert(flagfile_value->end(), flagfiles.begin(), + flagfiles.end()); + } + + success &= ReadFlagfiles(flagfiles, input_args); + + flags_internal::flagfile_needs_processing = false; + } + + // Similar to flagfile fromenv/tryfromemv can be set both + // programmatically and at runtime on a command line. Unlike flagfile these + // can't be recursive. + if (flags_internal::fromenv_needs_processing) { + auto flags_list = absl::GetFlag(FLAGS_fromenv); + + success &= ReadFlagsFromEnv(flags_list, input_args, true); + + flags_internal::fromenv_needs_processing = false; + } + + if (flags_internal::tryfromenv_needs_processing) { + auto flags_list = absl::GetFlag(FLAGS_tryfromenv); + + success &= ReadFlagsFromEnv(flags_list, input_args, false); + + flags_internal::tryfromenv_needs_processing = false; + } + + return success; +} + +// -------------------------------------------------------------------- + +void ResetGeneratorFlags(const std::vector<std::string>& flagfile_value) { + // Setting flagfile to the value which collates all the values set on a + // command line and programmatically. So if command line looked like + // --flagfile=f1 --flagfile=f2 the final value of the FLAGS_flagfile flag is + // going to be {"f1", "f2"} + if (!flagfile_value.empty()) { + absl::SetFlag(&FLAGS_flagfile, flagfile_value); + absl::MutexLock l(&flags_internal::processing_checks_guard); + flags_internal::flagfile_needs_processing = false; + } + + // fromenv/tryfromenv are set to <undefined> value. + if (!absl::GetFlag(FLAGS_fromenv).empty()) { + absl::SetFlag(&FLAGS_fromenv, {}); + } + if (!absl::GetFlag(FLAGS_tryfromenv).empty()) { + absl::SetFlag(&FLAGS_tryfromenv, {}); + } + + absl::MutexLock l(&flags_internal::processing_checks_guard); + flags_internal::fromenv_needs_processing = false; + flags_internal::tryfromenv_needs_processing = false; +} + +// -------------------------------------------------------------------- + +// Returns: +// success status +// deduced value +// We are also mutating curr_list in case if we need to get a hold of next +// argument in the input. +std::tuple<bool, absl::string_view> DeduceFlagValue(const CommandLineFlag& flag, + absl::string_view value, + bool is_negative, + bool is_empty_value, + ArgsList* curr_list) { + // Value is either an argument suffix after `=` in "--foo=<value>" + // or separate argument in case of "--foo" "<value>". + + // boolean flags have these forms: + // --foo + // --nofoo + // --foo=true + // --foo=false + // --nofoo=<value> is not supported + // --foo <value> is not supported + + // non boolean flags have these forms: + // --foo=<value> + // --foo <value> + // --nofoo is not supported + + if (flag.IsOfType<bool>()) { + if (value.empty()) { + if (is_empty_value) { + // "--bool_flag=" case + flags_internal::ReportUsageError( + absl::StrCat( + "Missing the value after assignment for the boolean flag '", + flag.Name(), "'"), + true); + return std::make_tuple(false, ""); + } + + // "--bool_flag" case + value = is_negative ? "0" : "1"; + } else if (is_negative) { + // "--nobool_flag=Y" case + flags_internal::ReportUsageError( + absl::StrCat("Negative form with assignment is not valid for the " + "boolean flag '", + flag.Name(), "'"), + true); + return std::make_tuple(false, ""); + } + } else if (is_negative) { + // "--noint_flag=1" case + flags_internal::ReportUsageError( + absl::StrCat("Negative form is not valid for the flag '", flag.Name(), + "'"), + true); + return std::make_tuple(false, ""); + } else if (value.empty() && (!is_empty_value)) { + if (curr_list->Size() == 1) { + // "--int_flag" case + flags_internal::ReportUsageError( + absl::StrCat("Missing the value for the flag '", flag.Name(), "'"), + true); + return std::make_tuple(false, ""); + } + + // "--int_flag" "10" case + curr_list->PopFront(); + value = curr_list->Front(); + + // Heuristic to detect the case where someone treats a std::string arg + // like a bool or just forgets to pass a value: + // --my_string_var --foo=bar + // We look for a flag of std::string type, whose value begins with a + // dash and corresponds to known flag or standalone --. + if (value[0] == '-' && flag.IsOfType<std::string>()) { + auto maybe_flag_name = std::get<0>(SplitNameAndValue(value.substr(1))); + + if (maybe_flag_name.empty() || + std::get<0>(LocateFlag(maybe_flag_name)) != nullptr) { + // "--string_flag" "--known_flag" case + ABSL_INTERNAL_LOG( + WARNING, + absl::StrCat("Did you really mean to set flag '", flag.Name(), + "' to the value '", value, "'?")); + } + } + } + + return std::make_tuple(true, value); +} + +// -------------------------------------------------------------------- + +bool CanIgnoreUndefinedFlag(absl::string_view flag_name) { + auto undefok = absl::GetFlag(FLAGS_undefok); + if (std::find(undefok.begin(), undefok.end(), flag_name) != undefok.end()) { + return true; + } + + if (absl::ConsumePrefix(&flag_name, "no") && + std::find(undefok.begin(), undefok.end(), flag_name) != undefok.end()) { + return true; + } + + return false; +} + +} // namespace + +// -------------------------------------------------------------------- + +std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], + ArgvListAction arg_list_act, + UsageFlagsAction usage_flag_act, + OnUndefinedFlag on_undef_flag) { + ABSL_INTERNAL_CHECK(argc > 0, "Missing argv[0]"); + + // This routine does not return anything since we abort on failure. + CheckDefaultValuesParsingRoundtrip(); + + std::vector<std::string> flagfile_value; + + std::vector<ArgsList> input_args; + input_args.push_back(ArgsList(argc, argv)); + + std::vector<char*> output_args; + std::vector<char*> positional_args; + output_args.reserve(argc); + + // This is the list of undefined flags. The element of the list is the pair + // consisting of boolean indicating if flag came from command line (vs from + // some flag file we've read) and flag name. + // TODO(rogeeff): Eliminate the first element in the pair after cleanup. + std::vector<std::pair<bool, std::string>> undefined_flag_names; + + // Set program invocation name if it is not set before. + if (ProgramInvocationName() == "UNKNOWN") { + flags_internal::SetProgramInvocationName(argv[0]); + } + output_args.push_back(argv[0]); + + // Iterate through the list of the input arguments. First level are arguments + // originated from argc/argv. Following levels are arguments originated from + // recursive parsing of flagfile(s). + bool success = true; + while (!input_args.empty()) { + // 10. First we process the built-in generator flags. + success &= HandleGeneratorFlags(&input_args, &flagfile_value); + + // 30. Select top-most (most recent) arguments list. If it is empty drop it + // and re-try. + ArgsList& curr_list = input_args.back(); + + curr_list.PopFront(); + + if (curr_list.Size() == 0) { + input_args.pop_back(); + continue; + } + + // 40. Pick up the front remaining argument in the current list. If current + // stack of argument lists contains only one element - we are processing an + // argument from the original argv. + absl::string_view arg(curr_list.Front()); + bool arg_from_argv = input_args.size() == 1; + + // 50. If argument does not start with - or is just "-" - this is + // positional argument. + if (!absl::ConsumePrefix(&arg, "-") || arg.empty()) { + ABSL_INTERNAL_CHECK(arg_from_argv, + "Flagfile cannot contain positional argument"); + + positional_args.push_back(argv[curr_list.FrontIndex()]); + continue; + } + + if (arg_from_argv && (arg_list_act == ArgvListAction::kKeepParsedArgs)) { + output_args.push_back(argv[curr_list.FrontIndex()]); + } + + // 60. Split the current argument on '=' to figure out the argument + // name and value. If flag name is empty it means we've got "--". value + // can be empty either if there were no '=' in argument std::string at all or + // an argument looked like "--foo=". In a latter case is_empty_value is + // true. + absl::string_view flag_name; + absl::string_view value; + bool is_empty_value = false; + + std::tie(flag_name, value, is_empty_value) = SplitNameAndValue(arg); + + // 70. "--" alone means what it does for GNU: stop flags parsing. We do + // not support positional arguments in flagfiles, so we just drop them. + if (flag_name.empty()) { + ABSL_INTERNAL_CHECK(arg_from_argv, + "Flagfile cannot contain positional argument"); + + curr_list.PopFront(); + break; + } + + // 80. Locate the flag based on flag name. Handle both --foo and --nofoo + CommandLineFlag* flag = nullptr; + bool is_negative = false; + std::tie(flag, is_negative) = LocateFlag(flag_name); + + if (flag == nullptr) { + if (on_undef_flag != OnUndefinedFlag::kIgnoreUndefined) { + undefined_flag_names.emplace_back(arg_from_argv, + std::string(flag_name)); + } + continue; + } + + // 90. Deduce flag's value (from this or next argument) + auto curr_index = curr_list.FrontIndex(); + bool value_success = true; + std::tie(value_success, value) = + DeduceFlagValue(*flag, value, is_negative, is_empty_value, &curr_list); + success &= value_success; + + // If above call consumed an argument, it was a standalone value + if (arg_from_argv && (arg_list_act == ArgvListAction::kKeepParsedArgs) && + (curr_index != curr_list.FrontIndex())) { + output_args.push_back(argv[curr_list.FrontIndex()]); + } + + // 100. Set the located flag to a new new value, unless it is retired. + // Setting retired flag fails, but we ignoring it here. + if (flag->IsRetired()) continue; + + std::string error; + if (!flag->SetFromString(value, SET_FLAGS_VALUE, kCommandLine, &error)) { + flags_internal::ReportUsageError(error, true); + success = false; + } + } + + for (const auto& flag_name : undefined_flag_names) { + if (CanIgnoreUndefinedFlag(flag_name.second)) continue; + + flags_internal::ReportUsageError( + absl::StrCat("Unknown command line flag '", flag_name.second, "'"), + true); + + success = false; + } + +#if ABSL_FLAGS_STRIP_NAMES + if (!success) { + flags_internal::ReportUsageError( + "NOTE: command line flags are disabled in this build", true); + } +#endif + + if (!success) { + flags_internal::HandleUsageFlags(std::cout); + std::exit(1); + } + + if (usage_flag_act == UsageFlagsAction::kHandleUsage) { + int exit_code = flags_internal::HandleUsageFlags(std::cout); + + if (exit_code != -1) { + std::exit(exit_code); + } + } + + ResetGeneratorFlags(flagfile_value); + + // Reinstate positional args which were intermixed with flags in the arguments + // list. + for (auto arg : positional_args) { + output_args.push_back(arg); + } + + // All the remaining arguments are positional. + if (!input_args.empty()) { + for (int arg_index = input_args.back().FrontIndex(); arg_index < argc; + ++arg_index) { + output_args.push_back(argv[arg_index]); + } + } + + return output_args; +} + +} // namespace flags_internal + +// -------------------------------------------------------------------- + +std::vector<char*> ParseCommandLine(int argc, char* argv[]) { + return flags_internal::ParseCommandLineImpl( + argc, argv, flags_internal::ArgvListAction::kRemoveParsedArgs, + flags_internal::UsageFlagsAction::kHandleUsage, + flags_internal::OnUndefinedFlag::kAbortIfUndefined); +} + +} // namespace absl diff --git a/absl/flags/parse.h b/absl/flags/parse.h new file mode 100644 index 000000000000..dbb751015686 --- /dev/null +++ b/absl/flags/parse.h @@ -0,0 +1,58 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: parse.h +// ----------------------------------------------------------------------------- +// +// This file defines the main parsing function for Abseil flags: +// `absl::ParseCommandLine()`. + +#ifndef ABSL_FLAGS_PARSE_H_ +#define ABSL_FLAGS_PARSE_H_ + +#include <string> +#include <vector> + +#include "absl/flags/internal/parse.h" + +namespace absl { + +// ParseCommandLine() +// +// Parses the set of command-line arguments passed in the `argc` (argument +// count) and `argv[]` (argument vector) parameters from `main()`, assigning +// values to any defined Abseil flags. (Any arguments passed after the +// flag-terminating delimiter (`--`) are treated as positional arguments and +// ignored.) +// +// Any command-line flags (and arguments to those flags) are parsed into Abseil +// Flag values, if those flags are defined. Any undefined flags will either +// return an error, or be ignored if that flag is designated using `undefok` to +// indicate "undefined is OK." +// +// Any command-line positional arguments not part of any command-line flag (or +// arguments to a flag) are returned in a vector, with the program invocation +// name at position 0 of that vector. (Note that this includes positional +// arguments after the flag-terminating delimiter `--`.) +// +// After all flags and flag arguments are parsed, this function looks for any +// built-in usage flags (e.g. `--help`), and if any were specified, it reports +// help messages and then exits the program. +std::vector<char*> ParseCommandLine(int argc, char* argv[]); + +} // namespace absl + +#endif // ABSL_FLAGS_PARSE_H_ diff --git a/absl/flags/parse_test.cc b/absl/flags/parse_test.cc new file mode 100644 index 000000000000..447a3bc7bdcb --- /dev/null +++ b/absl/flags/parse_test.cc @@ -0,0 +1,858 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/parse.h" + +#include <fstream> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/base/internal/scoped_set_env.h" +#include "absl/flags/flag.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/substitute.h" +#include "absl/types/span.h" + +#ifdef _WIN32 +#include <windows.h> +#endif + +namespace { + +using absl::base_internal::ScopedSetEnv; + +struct UDT { + UDT() = default; + UDT(const UDT&) = default; + UDT(int v) : value(v) {} // NOLINT + + int value; +}; + +bool AbslParseFlag(absl::string_view in, UDT* udt, std::string* err) { + if (in == "A") { + udt->value = 1; + return true; + } + if (in == "AAA") { + udt->value = 10; + return true; + } + + *err = "Use values A, AAA instead"; + return false; +} +std::string AbslUnparseFlag(const UDT& udt) { + return udt.value == 1 ? "A" : "AAA"; +} + +std::string GetTestTmpDirEnvVar(const char* const env_var_name) { +#ifdef _WIN32 + char buf[MAX_PATH]; + auto get_res = GetEnvironmentVariableA(env_var_name, buf, sizeof(buf)); + if (get_res >= sizeof(buf) || get_res == 0) { + return ""; + } + + return std::string(buf, get_res); +#else + const char* val = ::getenv(env_var_name); + if (val == nullptr) { + return ""; + } + + return val; +#endif +} + +const std::string& GetTestTempDir() { + static std::string* temp_dir_name = []() -> std::string* { + std::string* res = new std::string(GetTestTmpDirEnvVar("TEST_TMPDIR")); + + if (res->empty()) { + *res = GetTestTmpDirEnvVar("TMPDIR"); + } + + if (res->empty()) { +#ifdef _WIN32 + char temp_path_buffer[MAX_PATH]; + + auto len = GetTempPathA(MAX_PATH, temp_path_buffer); + if (len < MAX_PATH && len != 0) { + std::string temp_dir_name = absl::StrCat( + temp_path_buffer, "\\parse_test.", GetCurrentProcessId()); + if (CreateDirectoryA(temp_dir_name.c_str(), nullptr)) { + *res = temp_dir_name; + } + } +#else + char temp_dir_template[] = "/tmp/parse_test.XXXXXX"; + if (auto* unique_name = ::mkdtemp(temp_dir_template)) { + *res = unique_name; + } +#endif + + if (res->empty()) { + ABSL_INTERNAL_LOG(FATAL, + "Failed to make temporary directory for data files"); + } + } + +#ifdef _WIN32 + *res += "\\"; +#else + *res += "/"; +#endif + + return res; + }(); + + return *temp_dir_name; +} + +struct FlagfileData { + const absl::string_view file_name; + const absl::Span<const char* const> file_lines; +}; + +// clang-format off +constexpr const char* const ff1_data[] = { + "# comment ", + " # comment ", + "", + " ", + "--int_flag=-1", + " --string_flag=q2w2 ", + " ## ", + " --double_flag=0.1", + "--bool_flag=Y " +}; + +constexpr const char* const ff2_data[] = { + "# Setting legacy flag", + "--legacy_int=1111", + "--legacy_bool", + "--nobool_flag", + "--legacy_str=aqsw", + "--int_flag=100", + " ## =============" +}; +// clang-format on + +// Builds flagfile flag in the flagfile_flag buffer and returns it. This +// function also creates a temporary flagfile based on FlagfileData input. +// We create a flagfile in a temporary directory with the name specified in +// FlagfileData and populate it with lines specifed in FlagfileData. If $0 is +// referenced in any of the lines in FlagfileData they are replaced with +// temporary directory location. This way we can test inclusion of one flagfile +// from another flagfile. +const char* GetFlagfileFlag(const std::vector<FlagfileData>& ffd, + std::string* flagfile_flag) { + *flagfile_flag = "--flagfile="; + absl::string_view separator; + for (const auto& flagfile_data : ffd) { + std::string flagfile_name = + absl::StrCat(GetTestTempDir(), flagfile_data.file_name); + + std::ofstream flagfile_out(flagfile_name); + for (auto line : flagfile_data.file_lines) { + flagfile_out << absl::Substitute(line, GetTestTempDir()) << "\n"; + } + + absl::StrAppend(flagfile_flag, separator, flagfile_name); + separator = ","; + } + + return flagfile_flag->c_str(); +} + +} // namespace + +ABSL_FLAG(int, int_flag, 1, ""); +ABSL_FLAG(double, double_flag, 1.1, ""); +ABSL_FLAG(std::string, string_flag, "a", ""); +ABSL_FLAG(bool, bool_flag, false, ""); +ABSL_FLAG(UDT, udt_flag, -1, ""); +ABSL_RETIRED_FLAG(int, legacy_int, 1, ""); +ABSL_RETIRED_FLAG(bool, legacy_bool, false, ""); +ABSL_RETIRED_FLAG(std::string, legacy_str, "l", ""); + +namespace { + +namespace flags = absl::flags_internal; +using testing::ElementsAreArray; + +class ParseTest : public testing::Test { + private: + flags::FlagSaver flag_saver_; +}; + +// -------------------------------------------------------------------- + +template <int N> +std::vector<char*> InvokeParse(const char* (&in_argv)[N]) { + return absl::ParseCommandLine(N, const_cast<char**>(in_argv)); +} + +// -------------------------------------------------------------------- + +template <int N> +void TestParse(const char* (&in_argv)[N], int int_flag_value, + double double_flag_val, absl::string_view string_flag_val, + bool bool_flag_val, int exp_position_args = 0) { + auto out_args = InvokeParse(in_argv); + + EXPECT_EQ(out_args.size(), 1 + exp_position_args); + EXPECT_STREQ(out_args[0], "testbin"); + + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), int_flag_value); + EXPECT_NEAR(absl::GetFlag(FLAGS_double_flag), double_flag_val, 0.0001); + EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), string_flag_val); + EXPECT_EQ(absl::GetFlag(FLAGS_bool_flag), bool_flag_val); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestEmptyArgv) { + const char* in_argv[] = {"testbin"}; + + auto out_args = InvokeParse(in_argv); + + EXPECT_EQ(out_args.size(), 1); + EXPECT_STREQ(out_args[0], "testbin"); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestValidIntArg) { + const char* in_args1[] = { + "testbin", + "--int_flag=10", + }; + TestParse(in_args1, 10, 1.1, "a", false); + + const char* in_args2[] = { + "testbin", + "-int_flag=020", + }; + TestParse(in_args2, 20, 1.1, "a", false); + + const char* in_args3[] = { + "testbin", + "--int_flag", + "-30", + }; + TestParse(in_args3, -30, 1.1, "a", false); + + const char* in_args4[] = { + "testbin", + "-int_flag", + "0x21", + }; + TestParse(in_args4, 33, 1.1, "a", false); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestValidDoubleArg) { + const char* in_args1[] = { + "testbin", + "--double_flag=2.3", + }; + TestParse(in_args1, 1, 2.3, "a", false); + + const char* in_args2[] = { + "testbin", + "--double_flag=0x1.2", + }; + TestParse(in_args2, 1, 1.125, "a", false); + + const char* in_args3[] = { + "testbin", + "--double_flag", + "99.7", + }; + TestParse(in_args3, 1, 99.7, "a", false); + + const char* in_args4[] = { + "testbin", + "--double_flag", + "0x20.1", + }; + TestParse(in_args4, 1, 32.0625, "a", false); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestValidStringArg) { + const char* in_args1[] = { + "testbin", + "--string_flag=aqswde", + }; + TestParse(in_args1, 1, 1.1, "aqswde", false); + + const char* in_args2[] = { + "testbin", + "-string_flag=a=b=c", + }; + TestParse(in_args2, 1, 1.1, "a=b=c", false); + + const char* in_args3[] = { + "testbin", + "--string_flag", + "zaxscd", + }; + TestParse(in_args3, 1, 1.1, "zaxscd", false); + + const char* in_args4[] = { + "testbin", + "-string_flag", + "--int_flag", + }; + TestParse(in_args4, 1, 1.1, "--int_flag", false); + + const char* in_args5[] = { + "testbin", + "--string_flag", + "--no_a_flag=11", + }; + TestParse(in_args5, 1, 1.1, "--no_a_flag=11", false); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestValidBoolArg) { + const char* in_args1[] = { + "testbin", + "--bool_flag", + }; + TestParse(in_args1, 1, 1.1, "a", true); + + const char* in_args2[] = { + "testbin", + "--nobool_flag", + }; + TestParse(in_args2, 1, 1.1, "a", false); + + const char* in_args3[] = { + "testbin", + "--bool_flag=true", + }; + TestParse(in_args3, 1, 1.1, "a", true); + + const char* in_args4[] = { + "testbin", + "-bool_flag=false", + }; + TestParse(in_args4, 1, 1.1, "a", false); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestValidUDTArg) { + const char* in_args1[] = { + "testbin", + "--udt_flag=A", + }; + InvokeParse(in_args1); + + EXPECT_EQ(absl::GetFlag(FLAGS_udt_flag).value, 1); + + const char* in_args2[] = {"testbin", "--udt_flag", "AAA"}; + InvokeParse(in_args2); + + EXPECT_EQ(absl::GetFlag(FLAGS_udt_flag).value, 10); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestValidMultipleArg) { + const char* in_args1[] = { + "testbin", "--bool_flag", "--int_flag=2", + "--double_flag=0.1", "--string_flag=asd", + }; + TestParse(in_args1, 2, 0.1, "asd", true); + + const char* in_args2[] = { + "testbin", "--string_flag=", "--nobool_flag", "--int_flag", + "-011", "--double_flag", "-1e-2", + }; + TestParse(in_args2, -11, -0.01, "", false); + + const char* in_args3[] = { + "testbin", "--int_flag", "-0", "--string_flag", "\"\"", + "--bool_flag=true", "--double_flag=1e18", + }; + TestParse(in_args3, 0, 1e18, "\"\"", true); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestPositionalArgs) { + const char* in_args1[] = { + "testbin", + "p1", + "p2", + }; + TestParse(in_args1, 1, 1.1, "a", false, 2); + + auto out_args1 = InvokeParse(in_args1); + + EXPECT_STREQ(out_args1[1], "p1"); + EXPECT_STREQ(out_args1[2], "p2"); + + const char* in_args2[] = { + "testbin", + "--int_flag=2", + "p1", + }; + TestParse(in_args2, 2, 1.1, "a", false, 1); + + auto out_args2 = InvokeParse(in_args2); + + EXPECT_STREQ(out_args2[1], "p1"); + + const char* in_args3[] = {"testbin", "p1", "--int_flag=3", + "p2", "--bool_flag", "true"}; + TestParse(in_args3, 3, 1.1, "a", true, 3); + + auto out_args3 = InvokeParse(in_args3); + + EXPECT_STREQ(out_args3[1], "p1"); + EXPECT_STREQ(out_args3[2], "p2"); + EXPECT_STREQ(out_args3[3], "true"); + + const char* in_args4[] = { + "testbin", + "--", + "p1", + "p2", + }; + TestParse(in_args4, 3, 1.1, "a", true, 2); + + auto out_args4 = InvokeParse(in_args4); + + EXPECT_STREQ(out_args4[1], "p1"); + EXPECT_STREQ(out_args4[2], "p2"); + + const char* in_args5[] = { + "testbin", "p1", "--int_flag=4", "--", "--bool_flag", "false", "p2", + }; + TestParse(in_args5, 4, 1.1, "a", true, 4); + + auto out_args5 = InvokeParse(in_args5); + + EXPECT_STREQ(out_args5[1], "p1"); + EXPECT_STREQ(out_args5[2], "--bool_flag"); + EXPECT_STREQ(out_args5[3], "false"); + EXPECT_STREQ(out_args5[4], "p2"); +} + +// -------------------------------------------------------------------- + +using ParseDeathTest = ParseTest; + +TEST_F(ParseDeathTest, TestUndefinedArg) { + const char* in_args1[] = { + "testbin", + "--undefined_flag", + }; + EXPECT_DEATH(InvokeParse(in_args1), + "Unknown command line flag 'undefined_flag'"); + + const char* in_args2[] = { + "testbin", + "--noprefixed_flag", + }; + EXPECT_DEATH(InvokeParse(in_args2), + "Unknown command line flag 'noprefixed_flag'"); + + const char* in_args3[] = { + "testbin", + "--Int_flag=1", + }; + EXPECT_DEATH(InvokeParse(in_args3), "Unknown command line flag 'Int_flag'"); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseDeathTest, TestInvalidBoolFlagFormat) { + const char* in_args1[] = { + "testbin", + "--bool_flag=", + }; + EXPECT_DEATH( + InvokeParse(in_args1), + "Missing the value after assignment for the boolean flag 'bool_flag'"); + + const char* in_args2[] = { + "testbin", + "--nobool_flag=true", + }; + EXPECT_DEATH(InvokeParse(in_args2), + "Negative form with assignment is not valid for the boolean " + "flag 'bool_flag'"); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseDeathTest, TestInvalidNonBoolFlagFormat) { + const char* in_args1[] = { + "testbin", + "--nostring_flag", + }; + EXPECT_DEATH(InvokeParse(in_args1), + "Negative form is not valid for the flag 'string_flag'"); + + const char* in_args2[] = { + "testbin", + "--int_flag", + }; + EXPECT_DEATH(InvokeParse(in_args2), + "Missing the value for the flag 'int_flag'"); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseDeathTest, TestInvalidUDTFlagFormat) { + const char* in_args1[] = { + "testbin", + "--udt_flag=1", + }; + EXPECT_DEATH(InvokeParse(in_args1), + "Illegal value '1' specified for flag 'udt_flag'; Use values A, " + "AAA instead"); + + const char* in_args2[] = { + "testbin", + "--udt_flag", + "AA", + }; + EXPECT_DEATH(InvokeParse(in_args2), + "Illegal value 'AA' specified for flag 'udt_flag'; Use values " + "A, AAA instead"); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestLegacyFlags) { + const char* in_args1[] = { + "testbin", + "--legacy_int=11", + }; + TestParse(in_args1, 1, 1.1, "a", false); + + const char* in_args2[] = { + "testbin", + "--legacy_bool", + }; + TestParse(in_args2, 1, 1.1, "a", false); + + const char* in_args3[] = { + "testbin", "--legacy_int", "22", "--int_flag=2", + "--legacy_bool", "true", "--legacy_str", "--string_flag=qwe", + }; + TestParse(in_args3, 2, 1.1, "a", false, 1); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestSimpleValidFlagfile) { + std::string flagfile_flag; + + const char* in_args1[] = { + "testbin", + GetFlagfileFlag({{"parse_test.ff1", absl::MakeConstSpan(ff1_data)}}, + &flagfile_flag), + }; + TestParse(in_args1, -1, 0.1, "q2w2 ", true); + + const char* in_args2[] = { + "testbin", + GetFlagfileFlag({{"parse_test.ff2", absl::MakeConstSpan(ff2_data)}}, + &flagfile_flag), + }; + TestParse(in_args2, 100, 0.1, "q2w2 ", false); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestValidMultiFlagfile) { + std::string flagfile_flag; + + const char* in_args1[] = { + "testbin", + GetFlagfileFlag({{"parse_test.ff2", absl::MakeConstSpan(ff2_data)}, + {"parse_test.ff1", absl::MakeConstSpan(ff1_data)}}, + &flagfile_flag), + }; + TestParse(in_args1, -1, 0.1, "q2w2 ", true); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestFlagfileMixedWithRegularFlags) { + std::string flagfile_flag; + + const char* in_args1[] = { + "testbin", "--int_flag=3", + GetFlagfileFlag({{"parse_test.ff1", absl::MakeConstSpan(ff1_data)}}, + &flagfile_flag), + "-double_flag=0.2"}; + TestParse(in_args1, -1, 0.2, "q2w2 ", true); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestFlagfileInFlagfile) { + std::string flagfile_flag; + + constexpr const char* const ff3_data[] = { + "--flagfile=$0/parse_test.ff1", + "--flagfile=$0/parse_test.ff2", + }; + + const char* in_args1[] = { + "testbin", + GetFlagfileFlag({{"parse_test.ff3", absl::MakeConstSpan(ff3_data)}}, + &flagfile_flag), + }; + TestParse(in_args1, 100, 0.1, "q2w2 ", false); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseDeathTest, TestInvalidFlagfiles) { + std::string flagfile_flag; + + constexpr const char* const ff4_data[] = { + "--unknown_flag=10" + }; + + const char* in_args1[] = { + "testbin", + GetFlagfileFlag({{"parse_test.ff4", + absl::MakeConstSpan(ff4_data)}}, &flagfile_flag), + }; + EXPECT_DEATH(InvokeParse(in_args1), + "Unknown command line flag 'unknown_flag'"); + + constexpr const char* const ff5_data[] = { + "--int_flag 10", + }; + + const char* in_args2[] = { + "testbin", + GetFlagfileFlag({{"parse_test.ff5", + absl::MakeConstSpan(ff5_data)}}, &flagfile_flag), + }; + EXPECT_DEATH(InvokeParse(in_args2), + "Unknown command line flag 'int_flag 10'"); + + constexpr const char* const ff6_data[] = { + "--int_flag=10", "--", "arg1", "arg2", "arg3", + }; + + const char* in_args3[] = { + "testbin", + GetFlagfileFlag({{"parse_test.ff6", absl::MakeConstSpan(ff6_data)}}, + &flagfile_flag), + }; + EXPECT_DEATH(InvokeParse(in_args3), + "Flagfile can't contain position arguments or --"); + + const char* in_args4[] = { + "testbin", + "--flagfile=invalid_flag_file", + }; + EXPECT_DEATH(InvokeParse(in_args4), "Can't open flagfile invalid_flag_file"); + + constexpr const char* const ff7_data[] = { + "--int_flag=10", + "*bin*", + "--str_flag=aqsw", + }; + + const char* in_args5[] = { + "testbin", + GetFlagfileFlag({{"parse_test.ff7", absl::MakeConstSpan(ff7_data)}}, + &flagfile_flag), + }; + EXPECT_DEATH(InvokeParse(in_args5), + "Unexpected line in the flagfile .*: \\*bin\\*"); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestReadingRequiredFlagsFromEnv) { + const char* in_args1[] = {"testbin", + "--fromenv=int_flag,bool_flag,string_flag"}; + + ScopedSetEnv set_int_flag("FLAGS_int_flag", "33"); + ScopedSetEnv set_bool_flag("FLAGS_bool_flag", "True"); + ScopedSetEnv set_string_flag("FLAGS_string_flag", "AQ12"); + + TestParse(in_args1, 33, 1.1, "AQ12", true); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseDeathTest, TestReadingUnsetRequiredFlagsFromEnv) { + const char* in_args1[] = {"testbin", "--fromenv=int_flag"}; + + EXPECT_DEATH(InvokeParse(in_args1), + "FLAGS_int_flag not found in environment"); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseDeathTest, TestRecursiveFlagsFromEnv) { + const char* in_args1[] = {"testbin", "--fromenv=tryfromenv"}; + + ScopedSetEnv set_tryfromenv("FLAGS_tryfromenv", "int_flag"); + + EXPECT_DEATH(InvokeParse(in_args1), "Infinite recursion on flag tryfromenv"); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestReadingOptionalFlagsFromEnv) { + const char* in_args1[] = { + "testbin", "--tryfromenv=int_flag,bool_flag,string_flag,other_flag"}; + + ScopedSetEnv set_int_flag("FLAGS_int_flag", "17"); + ScopedSetEnv set_bool_flag("FLAGS_bool_flag", "Y"); + + TestParse(in_args1, 17, 1.1, "a", true); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestReadingFlagsFromEnvMoxedWithRegularFlags) { + const char* in_args1[] = { + "testbin", + "--bool_flag=T", + "--tryfromenv=int_flag,bool_flag", + "--int_flag=-21", + }; + + ScopedSetEnv set_int_flag("FLAGS_int_flag", "-15"); + ScopedSetEnv set_bool_flag("FLAGS_bool_flag", "F"); + + TestParse(in_args1, -21, 1.1, "a", false); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestKeepParsedArgs) { + const char* in_args1[] = { + "testbin", "arg1", "--bool_flag", + "--int_flag=211", "arg2", "--double_flag=1.1", + "--string_flag", "asd", "--", + "arg3", "arg4", + }; + + auto out_args1 = InvokeParse(in_args1); + + EXPECT_THAT( + out_args1, + ElementsAreArray({absl::string_view("testbin"), absl::string_view("arg1"), + absl::string_view("arg2"), absl::string_view("arg3"), + absl::string_view("arg4")})); + + auto out_args2 = flags::ParseCommandLineImpl( + 11, const_cast<char**>(in_args1), flags::ArgvListAction::kKeepParsedArgs, + flags::UsageFlagsAction::kHandleUsage, + flags::OnUndefinedFlag::kAbortIfUndefined); + + EXPECT_THAT( + out_args2, + ElementsAreArray({absl::string_view("testbin"), + absl::string_view("--bool_flag"), + absl::string_view("--int_flag=211"), + absl::string_view("--double_flag=1.1"), + absl::string_view("--string_flag"), + absl::string_view("asd"), absl::string_view("--"), + absl::string_view("arg1"), absl::string_view("arg2"), + absl::string_view("arg3"), absl::string_view("arg4")})); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, TestIgnoreUndefinedFlags) { + const char* in_args1[] = { + "testbin", + "arg1", + "--undef_flag=aa", + "--int_flag=21", + }; + + auto out_args1 = flags::ParseCommandLineImpl( + 4, const_cast<char**>(in_args1), flags::ArgvListAction::kRemoveParsedArgs, + flags::UsageFlagsAction::kHandleUsage, + flags::OnUndefinedFlag::kIgnoreUndefined); + + EXPECT_THAT(out_args1, ElementsAreArray({absl::string_view("testbin"), + absl::string_view("arg1")})); + + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 21); + + const char* in_args2[] = { + "testbin", + "arg1", + "--undef_flag=aa", + "--string_flag=AA", + }; + + auto out_args2 = flags::ParseCommandLineImpl( + 4, const_cast<char**>(in_args2), flags::ArgvListAction::kKeepParsedArgs, + flags::UsageFlagsAction::kHandleUsage, + flags::OnUndefinedFlag::kIgnoreUndefined); + + EXPECT_THAT( + out_args2, + ElementsAreArray( + {absl::string_view("testbin"), absl::string_view("--undef_flag=aa"), + absl::string_view("--string_flag=AA"), absl::string_view("arg1")})); + + EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), "AA"); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseDeathTest, TestHelpFlagHandling) { + const char* in_args1[] = { + "testbin", + "--help", + }; + + EXPECT_EXIT(InvokeParse(in_args1), testing::ExitedWithCode(1), ""); + + const char* in_args2[] = { + "testbin", + "--help", + "--int_flag=3", + }; + + auto out_args2 = flags::ParseCommandLineImpl( + 3, const_cast<char**>(in_args2), flags::ArgvListAction::kRemoveParsedArgs, + flags::UsageFlagsAction::kIgnoreUsage, + flags::OnUndefinedFlag::kAbortIfUndefined); + + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 3); +} + +} // namespace diff --git a/absl/flags/usage_config.cc b/absl/flags/usage_config.cc new file mode 100644 index 000000000000..d34ecfc82c7b --- /dev/null +++ b/absl/flags/usage_config.cc @@ -0,0 +1,152 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/usage_config.h" + +#include <iostream> +#include <memory> + +#include "absl/base/attributes.h" +#include "absl/flags/internal/path_util.h" +#include "absl/flags/internal/program_name.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/strip.h" +#include "absl/synchronization/mutex.h" + +extern "C" { + +// Additional report of fatal usage error message before we std::exit. Error is +// fatal if is_fatal argument to ReportUsageError is true. +ABSL_ATTRIBUTE_WEAK void AbslInternalReportFatalUsageError(absl::string_view) {} + +} // extern "C" + +namespace absl { +namespace flags_internal { + +namespace { + +// -------------------------------------------------------------------- +// Returns true if flags defined in the filename should be reported with +// -helpshort flag. + +bool ContainsHelpshortFlags(absl::string_view filename) { + // By default we only want flags in binary's main. We expect the main + // routine to reside in <program>.cc or <program>-main.cc or + // <program>_main.cc, where the <program> is the name of the binary. + auto suffix = flags_internal::Basename(filename); + if (!absl::ConsumePrefix(&suffix, + flags_internal::ShortProgramInvocationName())) + return false; + return absl::StartsWith(suffix, ".") || absl::StartsWith(suffix, "-main.") || + absl::StartsWith(suffix, "_main."); +} + +// -------------------------------------------------------------------- +// Returns true if flags defined in the filename should be reported with +// -helppackage flag. + +bool ContainsHelppackageFlags(absl::string_view filename) { + // TODO(rogeeff): implement properly when registry is available. + return ContainsHelpshortFlags(filename); +} + +// -------------------------------------------------------------------- +// Generates program version information into supplied output. + +std::string VersionString() { + std::string version_str(flags_internal::ShortProgramInvocationName()); + + version_str += "\n"; + +#if !defined(NDEBUG) + version_str += "Debug build (NDEBUG not #defined)\n"; +#endif + + return version_str; +} + +// -------------------------------------------------------------------- +// Normalizes the filename specific to the build system/filesystem used. + +std::string NormalizeFilename(absl::string_view filename) { + // Skip any leading slashes + auto pos = filename.find_first_not_of("\\/"); + if (pos == absl::string_view::npos) return ""; + + filename.remove_prefix(pos); + return std::string(filename); +} + +// -------------------------------------------------------------------- + +ABSL_CONST_INIT absl::Mutex custom_usage_config_guard(absl::kConstInit); +ABSL_CONST_INIT FlagsUsageConfig* custom_usage_config + GUARDED_BY(custom_usage_config_guard) = nullptr; + +} // namespace + +FlagsUsageConfig GetUsageConfig() { + absl::MutexLock l(&custom_usage_config_guard); + + if (custom_usage_config) return *custom_usage_config; + + FlagsUsageConfig default_config; + default_config.contains_helpshort_flags = &ContainsHelpshortFlags; + default_config.contains_help_flags = &ContainsHelppackageFlags; + default_config.contains_helppackage_flags = &ContainsHelppackageFlags; + default_config.version_string = &VersionString; + default_config.normalize_filename = &NormalizeFilename; + + return default_config; +} + +void ReportUsageError(absl::string_view msg, bool is_fatal) { + std::cerr << "ERROR: " << msg << std::endl; + + if (is_fatal) { + AbslInternalReportFatalUsageError(msg); + } +} + +} // namespace flags_internal + +void SetFlagsUsageConfig(FlagsUsageConfig usage_config) { + absl::MutexLock l(&flags_internal::custom_usage_config_guard); + + if (!usage_config.contains_helpshort_flags) + usage_config.contains_helpshort_flags = + flags_internal::ContainsHelpshortFlags; + + if (!usage_config.contains_help_flags) + usage_config.contains_help_flags = flags_internal::ContainsHelppackageFlags; + + if (!usage_config.contains_helppackage_flags) + usage_config.contains_helppackage_flags = + flags_internal::ContainsHelppackageFlags; + + if (!usage_config.version_string) + usage_config.version_string = flags_internal::VersionString; + + if (!usage_config.normalize_filename) + usage_config.normalize_filename = flags_internal::NormalizeFilename; + + if (flags_internal::custom_usage_config) + *flags_internal::custom_usage_config = usage_config; + else + flags_internal::custom_usage_config = new FlagsUsageConfig(usage_config); +} + +} // namespace absl diff --git a/absl/flags/usage_config.h b/absl/flags/usage_config.h new file mode 100644 index 000000000000..ee5016a9ee30 --- /dev/null +++ b/absl/flags/usage_config.h @@ -0,0 +1,131 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: usage_config.h +// ----------------------------------------------------------------------------- +// +// This file defines the main usage reporting configuration interfaces and +// documents Abseil's supported built-in usage flags. If these flags are found +// when parsing a command-line, Abseil will exit the program and display +// appropriate help messages. +#ifndef ABSL_FLAGS_USAGE_CONFIG_H_ +#define ABSL_FLAGS_USAGE_CONFIG_H_ + +#include <functional> +#include <string> + +#include "absl/strings/string_view.h" + +// ----------------------------------------------------------------------------- +// Built-in Usage Flags +// ----------------------------------------------------------------------------- +// +// Abseil supports the following built-in usage flags. When passed, these flags +// exit the program and : +// +// * --help +// Shows help on important flags for this binary +// * --helpfull +// Shows help on all flags +// * --helpshort +// Shows help on only the main module for this program +// * --helppackage +// Shows help on all modules in the main package +// * --version +// Shows the version and build info for this binary and exits +// * --only_check_args +// Exits after checking all flags +// * --helpon +// Shows help on the modules named by this flag value +// * --helpmatch +// Shows help on modules whose name contains the specified substring + +namespace absl { + +namespace flags_internal { +using FlagKindFilter = std::function<bool (absl::string_view)>; +} // namespace flags_internal + +// FlagsUsageConfig +// +// This structure contains the collection of callbacks for changing the behavior +// of the usage reporting routines in Abseil Flags. +struct FlagsUsageConfig { + // Returns true if flags defined in the given source code file should be + // reported with --helpshort flag. For example, if the file + // "path/to/my/code.cc" defines the flag "--my_flag", and + // contains_helpshort_flags("path/to/my/code.cc") returns true, invoking the + // program with --helpshort will include information about --my_flag in the + // program output. + flags_internal::FlagKindFilter contains_helpshort_flags; + + // Returns true if flags defined in the filename should be reported with + // --help flag. For example, if the file + // "path/to/my/code.cc" defines the flag "--my_flag", and + // contains_help_flags("path/to/my/code.cc") returns true, invoking the + // program with --help will include information about --my_flag in the + // program output. + flags_internal::FlagKindFilter contains_help_flags; + + // Returns true if flags defined in the filename should be reported with + // --helppackage flag. For example, if the file + // "path/to/my/code.cc" defines the flag "--my_flag", and + // contains_helppackage_flags("path/to/my/code.cc") returns true, invoking the + // program with --helppackage will include information about --my_flag in the + // program output. + flags_internal::FlagKindFilter contains_helppackage_flags; + + // Generates std::string containing program version. This is the std::string reported + // when user specifies --version in a command line. + std::function<std::string()> version_string; + + // Normalizes the filename specific to the build system/filesystem used. This + // routine is used when we report the information about the flag definition + // location. For instance, if your build resides at some location you do not + // want to expose in the usage output, you can trim it to show only relevant + // part. + // For example: + // normalize_filename("/my_company/some_long_path/src/project/file.cc") + // might produce + // "project/file.cc". + std::function<std::string (absl::string_view)> normalize_filename; +}; + +// SetFlagsUsageConfig() +// +// Sets the usage reporting configuration callbacks. If any of the callbacks are +// not set in usage_config instance, then the default value of the callback is +// used. +void SetFlagsUsageConfig(FlagsUsageConfig usage_config); + +namespace flags_internal { + +FlagsUsageConfig GetUsageConfig(); + +void ReportUsageError(absl::string_view msg, bool is_fatal); + +} // namespace flags_internal +} // namespace absl + +extern "C" { + +// Additional report of fatal usage error message before we std::exit. Error is +// fatal if is_fatal argument to ReportUsageError is true. +void AbslInternalReportFatalUsageError(absl::string_view); + +} // extern "C" + +#endif // ABSL_FLAGS_USAGE_CONFIG_H_ diff --git a/absl/flags/usage_config_test.cc b/absl/flags/usage_config_test.cc new file mode 100644 index 000000000000..3bde13af8f84 --- /dev/null +++ b/absl/flags/usage_config_test.cc @@ -0,0 +1,198 @@ +// +// Copyright 2019 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/usage_config.h" + +#include "gtest/gtest.h" +#include "absl/flags/internal/path_util.h" +#include "absl/flags/internal/program_name.h" +#include "absl/strings/match.h" + +namespace { + +class FlagsUsageConfigTest : public testing::Test { + protected: + void SetUp() override { + // Install Default config for the use on this unit test. + // Binary may install a custom config before tests are run. + absl::FlagsUsageConfig default_config; + absl::SetFlagsUsageConfig(default_config); + } +}; + +namespace flags = absl::flags_internal; + +bool TstContainsHelpshortFlags(absl::string_view f) { + return absl::StartsWith(flags::Basename(f), "progname."); +} + +bool TstContainsHelppackageFlags(absl::string_view f) { + return absl::EndsWith(flags::Package(f), "aaa/"); +} + +bool TstContainsHelpFlags(absl::string_view f) { + return absl::EndsWith(flags::Package(f), "zzz/"); +} + +std::string TstVersionString() { return "program 1.0.0"; } + +std::string TstNormalizeFilename(absl::string_view filename) { + return std::string(filename.substr(2)); +} + +void TstReportUsageMessage(absl::string_view msg) {} + +// -------------------------------------------------------------------- + +TEST_F(FlagsUsageConfigTest, TestGetSetFlagsUsageConfig) { + EXPECT_TRUE(flags::GetUsageConfig().contains_helpshort_flags); + EXPECT_TRUE(flags::GetUsageConfig().contains_help_flags); + EXPECT_TRUE(flags::GetUsageConfig().contains_helppackage_flags); + EXPECT_TRUE(flags::GetUsageConfig().version_string); + EXPECT_TRUE(flags::GetUsageConfig().normalize_filename); + + absl::FlagsUsageConfig empty_config; + empty_config.contains_helpshort_flags = &TstContainsHelpshortFlags; + empty_config.contains_help_flags = &TstContainsHelpFlags; + empty_config.contains_helppackage_flags = &TstContainsHelppackageFlags; + empty_config.version_string = &TstVersionString; + empty_config.normalize_filename = &TstNormalizeFilename; + absl::SetFlagsUsageConfig(empty_config); + + EXPECT_TRUE(flags::GetUsageConfig().contains_helpshort_flags); + EXPECT_TRUE(flags::GetUsageConfig().contains_help_flags); + EXPECT_TRUE(flags::GetUsageConfig().contains_helppackage_flags); + EXPECT_TRUE(flags::GetUsageConfig().version_string); + EXPECT_TRUE(flags::GetUsageConfig().normalize_filename); +} + +// -------------------------------------------------------------------- + +TEST_F(FlagsUsageConfigTest, TestContainsHelpshortFlags) { + flags::SetProgramInvocationName("usage_config_test"); + + auto config = flags::GetUsageConfig(); + EXPECT_TRUE(config.contains_helpshort_flags("adir/cd/usage_config_test.cc")); + EXPECT_TRUE( + config.contains_helpshort_flags("aaaa/usage_config_test-main.cc")); + EXPECT_TRUE(config.contains_helpshort_flags("abc/usage_config_test_main.cc")); + EXPECT_FALSE(config.contains_helpshort_flags("usage_config_main.cc")); + + absl::FlagsUsageConfig empty_config; + empty_config.contains_helpshort_flags = &TstContainsHelpshortFlags; + absl::SetFlagsUsageConfig(empty_config); + + EXPECT_TRUE( + flags::GetUsageConfig().contains_helpshort_flags("aaa/progname.cpp")); + EXPECT_FALSE( + flags::GetUsageConfig().contains_helpshort_flags("aaa/progmane.cpp")); +} + +// -------------------------------------------------------------------- + +TEST_F(FlagsUsageConfigTest, TestContainsHelpFlags) { + flags::SetProgramInvocationName("usage_config_test"); + + auto config = flags::GetUsageConfig(); + EXPECT_TRUE(config.contains_help_flags("zzz/usage_config_test.cc")); + EXPECT_TRUE( + config.contains_help_flags("bdir/a/zzz/usage_config_test-main.cc")); + EXPECT_TRUE( + config.contains_help_flags("//aqse/zzz/usage_config_test_main.cc")); + EXPECT_FALSE(config.contains_help_flags("zzz/aa/usage_config_main.cc")); + + absl::FlagsUsageConfig empty_config; + empty_config.contains_help_flags = &TstContainsHelpFlags; + absl::SetFlagsUsageConfig(empty_config); + + EXPECT_TRUE(flags::GetUsageConfig().contains_help_flags("zzz/main-body.c")); + EXPECT_FALSE( + flags::GetUsageConfig().contains_help_flags("zzz/dir/main-body.c")); +} + +// -------------------------------------------------------------------- + +TEST_F(FlagsUsageConfigTest, TestContainsHelppackageFlags) { + flags::SetProgramInvocationName("usage_config_test"); + + auto config = flags::GetUsageConfig(); + EXPECT_TRUE(config.contains_helppackage_flags("aaa/usage_config_test.cc")); + EXPECT_TRUE( + config.contains_helppackage_flags("bbdir/aaa/usage_config_test-main.cc")); + EXPECT_TRUE(config.contains_helppackage_flags( + "//aqswde/aaa/usage_config_test_main.cc")); + EXPECT_FALSE(config.contains_helppackage_flags("aadir/usage_config_main.cc")); + + absl::FlagsUsageConfig empty_config; + empty_config.contains_helppackage_flags = &TstContainsHelppackageFlags; + absl::SetFlagsUsageConfig(empty_config); + + EXPECT_TRUE( + flags::GetUsageConfig().contains_helppackage_flags("aaa/main-body.c")); + EXPECT_FALSE( + flags::GetUsageConfig().contains_helppackage_flags("aadir/main-body.c")); +} + +// -------------------------------------------------------------------- + +TEST_F(FlagsUsageConfigTest, TestVersionString) { + flags::SetProgramInvocationName("usage_config_test"); + +#ifdef NDEBUG + std::string expected_output = "usage_config_test\n"; +#else + std::string expected_output = + "usage_config_test\nDebug build (NDEBUG not #defined)\n"; +#endif + + EXPECT_EQ(flags::GetUsageConfig().version_string(), expected_output); + + absl::FlagsUsageConfig empty_config; + empty_config.version_string = &TstVersionString; + absl::SetFlagsUsageConfig(empty_config); + + EXPECT_EQ(flags::GetUsageConfig().version_string(), "program 1.0.0"); +} + +// -------------------------------------------------------------------- + +TEST_F(FlagsUsageConfigTest, TestNormalizeFilename) { + // This tests the default implementation. + EXPECT_EQ(flags::GetUsageConfig().normalize_filename("a/a.cc"), "a/a.cc"); + EXPECT_EQ(flags::GetUsageConfig().normalize_filename("/a/a.cc"), "a/a.cc"); + EXPECT_EQ(flags::GetUsageConfig().normalize_filename("///a/a.cc"), "a/a.cc"); + EXPECT_EQ(flags::GetUsageConfig().normalize_filename("/"), ""); + + // This tests that the custom implementation is called. + absl::FlagsUsageConfig empty_config; + empty_config.normalize_filename = &TstNormalizeFilename; + absl::SetFlagsUsageConfig(empty_config); + + EXPECT_EQ(flags::GetUsageConfig().normalize_filename("a/a.cc"), "a.cc"); + EXPECT_EQ(flags::GetUsageConfig().normalize_filename("aaa/a.cc"), "a/a.cc"); + + // This tests that the default implementation is called. + empty_config.normalize_filename = nullptr; + absl::SetFlagsUsageConfig(empty_config); + + EXPECT_EQ(flags::GetUsageConfig().normalize_filename("a/a.cc"), "a/a.cc"); + EXPECT_EQ(flags::GetUsageConfig().normalize_filename("/a/a.cc"), "a/a.cc"); + EXPECT_EQ(flags::GetUsageConfig().normalize_filename("///a/a.cc"), "a/a.cc"); + EXPECT_EQ(flags::GetUsageConfig().normalize_filename("\\a\\a.cc"), "a\\a.cc"); + EXPECT_EQ(flags::GetUsageConfig().normalize_filename("//"), ""); + EXPECT_EQ(flags::GetUsageConfig().normalize_filename("\\\\"), ""); +} + +} // namespace |