tool: basic integration test So far all it does is spot check some help pages, but in the future we should augment it to test functionality too. For now that's not a big deal because every tool function is covered in either master_migration-itest or master_failover-itest.
Change-Id: Ib386882c1874e987d5824cfe742cc86627cd9eaa Reviewed-on: http://gerrit.cloudera.org:8080/4058 Tested-by: Kudu Jenkins Reviewed-by: Todd Lipcon <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/kudu/repo Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/7321b38b Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/7321b38b Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/7321b38b Branch: refs/heads/master Commit: 7321b38be0303876fcc71b5c73e3e6eddbbdf012 Parents: a1e6b88 Author: Adar Dembo <[email protected]> Authored: Thu Aug 18 20:50:39 2016 -0700 Committer: Todd Lipcon <[email protected]> Committed: Thu Aug 25 21:46:35 2016 +0000 ---------------------------------------------------------------------- build-support/dist_test.py | 13 ++- src/kudu/tools/CMakeLists.txt | 3 + src/kudu/tools/kudu-tool-test.cc | 154 ++++++++++++++++++++++++++++++++++ src/kudu/util/test_macros.h | 21 ++++- 4 files changed, 188 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/kudu/blob/7321b38b/build-support/dist_test.py ---------------------------------------------------------------------- diff --git a/build-support/dist_test.py b/build-support/dist_test.py index f56513f..d391cf4 100755 --- a/build-support/dist_test.py +++ b/build-support/dist_test.py @@ -182,7 +182,10 @@ def ldd_deps(exe): If the provided 'exe' is not a binary executable, returns an empty list. """ - if exe.endswith(".sh"): + if (exe.endswith(".pl") or + exe.endswith(".py") or + exe.endswith(".sh") or + exe.endswith(".txt")): return [] p = subprocess.Popen(["ldd", exe], stdout=subprocess.PIPE) out, err = p.communicate() @@ -243,7 +246,13 @@ def create_archive_input(staging, argv, if os.path.isdir(d): d += "/" deps.append(d) - for d in deps: + # DEPS_FOR_ALL may include binaries whose dependencies are not dependencies + # of the test executable. We must include those dependencies in the archive + # for the binaries to be usable. + deps.extend(ldd_deps(d)) + + # Deduplicate dependencies included via DEPS_FOR_ALL. + for d in set(deps): # System libraries will end up being relative paths out # of the build tree. We need to copy those into the build # tree somewhere. http://git-wip-us.apache.org/repos/asf/kudu/blob/7321b38b/src/kudu/tools/CMakeLists.txt ---------------------------------------------------------------------- diff --git a/src/kudu/tools/CMakeLists.txt b/src/kudu/tools/CMakeLists.txt index c66b314..ce361f0 100644 --- a/src/kudu/tools/CMakeLists.txt +++ b/src/kudu/tools/CMakeLists.txt @@ -115,6 +115,9 @@ ADD_KUDU_TEST(ksck_remote-test) ADD_KUDU_TEST(kudu-admin-test) ADD_KUDU_TEST_DEPENDENCIES(kudu-admin-test kudu-admin) +ADD_KUDU_TEST(kudu-tool-test) +ADD_KUDU_TEST_DEPENDENCIES(kudu-tool-test + kudu) ADD_KUDU_TEST(kudu-ts-cli-test) ADD_KUDU_TEST_DEPENDENCIES(kudu-ts-cli-test kudu-ts-cli) http://git-wip-us.apache.org/repos/asf/kudu/blob/7321b38b/src/kudu/tools/kudu-tool-test.cc ---------------------------------------------------------------------- diff --git a/src/kudu/tools/kudu-tool-test.cc b/src/kudu/tools/kudu-tool-test.cc new file mode 100644 index 0000000..2700fb1 --- /dev/null +++ b/src/kudu/tools/kudu-tool-test.cc @@ -0,0 +1,154 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +// +// http://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 <string> +#include <vector> + +#include <gtest/gtest.h> +#include <glog/stl_logging.h> + +#include "kudu/gutil/strings/split.h" +#include "kudu/gutil/strings/substitute.h" +#include "kudu/util/env.h" +#include "kudu/util/path_util.h" +#include "kudu/util/subprocess.h" +#include "kudu/util/test_macros.h" +#include "kudu/util/test_util.h" + +namespace kudu { +namespace tools { + +using std::string; +using std::vector; + +class ToolTest : public KuduTest { + public: + ToolTest() { + string exe; + CHECK_OK(env_->GetExecutablePath(&exe)); + string bin_root = DirName(exe); + string tool_path = JoinPathSegments(bin_root, "kudu"); + CHECK(env_->FileExists(tool_path)) << "kudu tool not found at " << tool_path; + tool_path_ = tool_path; + } + + Status RunTool(const string& arg_str, + vector<string>* stdout_lines, + vector<string>* stderr_lines) const { + vector<string> args = { tool_path_ }; + vector<string> more_args = strings::Split(arg_str, " ", + strings::SkipEmpty()); + args.insert(args.end(), more_args.begin(), more_args.end()); + + string stdout; + string stderr; + Status s = Subprocess::Call(args, &stdout, &stderr); + StripWhiteSpace(&stdout); + StripWhiteSpace(&stderr); + *stdout_lines = strings::Split(stdout, "\n", strings::SkipEmpty()); + *stderr_lines = strings::Split(stderr, "\n", strings::SkipEmpty()); + return s; + + } + + void RunTestHelp(const string& arg_str, + const vector<string>& regexes, + const Status& expected_status = Status::OK()) const { + vector<string> stdout; + vector<string> stderr; + Status s = RunTool(arg_str, &stdout, &stderr); + SCOPED_TRACE(stdout); + SCOPED_TRACE(stderr); + + // These are always true for showing help. + ASSERT_TRUE(s.IsRuntimeError()); + ASSERT_TRUE(stdout.empty()); + ASSERT_FALSE(stderr.empty()); + + // If it was an invalid command, the usage string is on the second line. + int usage_idx = 0; + if (!expected_status.ok()) { + ASSERT_EQ(expected_status.ToString(), stderr[0]); + usage_idx = 1; + } + ASSERT_EQ(0, stderr[usage_idx].find("Usage: ")); + + // Strip away everything up to the usage string to test for regexes. + vector<string> remaining_lines; + for (int i = usage_idx + 1; i < stderr.size(); i++) { + remaining_lines.push_back(stderr[i]); + } + for (const auto& r : regexes) { + ASSERT_STRINGS_ANY_MATCH(remaining_lines, r); + } + } + + private: + string tool_path_; +}; + +TEST_F(ToolTest, TestTopLevelHelp) { + const vector<string> kTopLevelRegexes = { + "fs.*Kudu filesystem", + "pbc.*protobuf container", + "tablet.*Kudu replica" + }; + NO_FATALS(RunTestHelp("", kTopLevelRegexes)); + NO_FATALS(RunTestHelp("--help", kTopLevelRegexes)); + NO_FATALS(RunTestHelp("not_a_mode", kTopLevelRegexes, + Status::InvalidArgument("unknown command 'not_a_mode'"))); +} + +TEST_F(ToolTest, TestModeHelp) { + { + const vector<string> kFsModeRegexes = { + "format.*new Kudu filesystem", + "print_uuid.*UUID of a Kudu filesystem" + }; + NO_FATALS(RunTestHelp("fs", kFsModeRegexes)); + NO_FATALS(RunTestHelp("fs not_a_mode", kFsModeRegexes, + Status::InvalidArgument("unknown command 'not_a_mode'"))); + } + { + const vector<string> kTabletModeRegexes = { + "cmeta.*consensus metadata file", + "copy.*Copy a replica" + }; + NO_FATALS(RunTestHelp("tablet", kTabletModeRegexes)); + } + { + const vector<string> kCmetaModeRegexes = { + "print_replica_uuids.*Print all replica UUIDs", + "rewrite_raft_config.*Rewrite a replica" + }; + NO_FATALS(RunTestHelp("tablet cmeta", kCmetaModeRegexes)); + } +} + +TEST_F(ToolTest, TestActionHelp) { + const vector<string> kFormatActionRegexes = { + "-fs_wal_dir \\(Directory", + "-fs_data_dirs \\(Comma-separated list", + "-uuid \\(The uuid" + }; + NO_FATALS(RunTestHelp("fs format --help", kFormatActionRegexes)); + NO_FATALS(RunTestHelp("fs format extra_arg", kFormatActionRegexes, + Status::InvalidArgument("too many arguments: 'extra_arg'"))); +} + +} // namespace tools +} // namespace kudu http://git-wip-us.apache.org/repos/asf/kudu/blob/7321b38b/src/kudu/util/test_macros.h ---------------------------------------------------------------------- diff --git a/src/kudu/util/test_macros.h b/src/kudu/util/test_macros.h index e7950a9..a5f151f 100644 --- a/src/kudu/util/test_macros.h +++ b/src/kudu/util/test_macros.h @@ -63,7 +63,9 @@ ASSERT_THAT(str, testing::ContainsRegex(pattern)) // Batched substring regular expressions in extended regex (POSIX) syntax. -#define ASSERT_STRINGS_MATCH(strings, pattern) do { \ +// +// All strings must match the pattern. +#define ASSERT_STRINGS_ALL_MATCH(strings, pattern) do { \ const auto& _strings = (strings); \ const auto& _pattern = (pattern); \ int _str_idx = 0; \ @@ -75,6 +77,23 @@ } \ } while (0) +// Batched substring regular expressions in extended regex (POSIX) syntax. +// +// At least one string must match the pattern. +#define ASSERT_STRINGS_ANY_MATCH(strings, pattern) do { \ + const auto& _strings = (strings); \ + const auto& _pattern = (pattern); \ + bool matched = false; \ + for (const auto& str : _strings) { \ + if (testing::internal::RE::PartialMatch(str, testing::internal::RE(_pattern))) { \ + matched = true; \ + break; \ + } \ + } \ + ASSERT_TRUE(matched) \ + << "not one string matched pattern " << _pattern; \ +} while (0) + #define ASSERT_FILE_EXISTS(env, path) do { \ std::string _s = path; \ ASSERT_TRUE(env->FileExists(_s)) \
