Add verity open benchmark test

PiperOrigin-RevId: 386533065
This commit is contained in:
Chong Cai 2021-07-23 14:33:27 -07:00 committed by gVisor bot
parent 3d0a930005
commit d247938363
10 changed files with 134 additions and 39 deletions

View File

@ -139,3 +139,10 @@ syscall_test(
debug = False, debug = False,
test = "//test/perf/linux:write_benchmark", test = "//test/perf/linux:write_benchmark",
) )
syscall_test(
size = "large",
debug = False,
test = "//test/perf/linux:verity_open_benchmark",
vfs1 = False,
)

View File

@ -370,3 +370,22 @@ cc_binary(
"//test/util:test_main", "//test/util:test_main",
], ],
) )
cc_binary(
name = "verity_open_benchmark",
testonly = 1,
srcs = [
"verity_open_benchmark.cc",
],
deps = [
gbenchmark,
gtest,
"//test/util:capability_util",
"//test/util:fs_util",
"//test/util:logging",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:verity_util",
],
)

View File

@ -0,0 +1,73 @@
// Copyright 2021 The gVisor 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
//
// 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 <fcntl.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <unistd.h>
#include <memory>
#include <string>
#include <vector>
#include "gtest/gtest.h"
#include "benchmark/benchmark.h"
#include "test/util/capability_util.h"
#include "test/util/fs_util.h"
#include "test/util/logging.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
#include "test/util/verity_util.h"
namespace gvisor {
namespace testing {
namespace {
void BM_Open(benchmark::State& state) {
SKIP_IF(IsRunningWithVFS1());
const int size = state.range(0);
std::vector<TempPath> cache;
std::vector<EnableTarget> targets;
// Mount a tmpfs file system to be wrapped by a verity fs.
TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
TEST_CHECK(mount("", dir.path().c_str(), "tmpfs", 0, "") == 0);
for (int i = 0; i < size; i++) {
auto path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
targets.emplace_back(
EnableTarget(std::string(Basename(path.path())), O_RDONLY));
cache.emplace_back(std::move(path));
}
std::string verity_dir =
TEST_CHECK_NO_ERRNO_AND_VALUE(MountVerity(dir.path(), targets));
unsigned int seed = 1;
for (auto _ : state) {
const int chosen = rand_r(&seed) % size;
int fd = open(JoinPath(verity_dir, targets[chosen].path).c_str(), O_RDONLY);
TEST_CHECK(fd != -1);
close(fd);
}
}
BENCHMARK(BM_Open)->Range(1, 128)->UseRealTime();
} // namespace
} // namespace testing
} // namespace gvisor

View File

@ -135,6 +135,7 @@ def syscall_test(
add_overlay = False, add_overlay = False,
add_uds_tree = False, add_uds_tree = False,
add_hostinet = False, add_hostinet = False,
vfs1 = True,
vfs2 = True, vfs2 = True,
fuse = False, fuse = False,
debug = True, debug = True,
@ -148,6 +149,7 @@ def syscall_test(
add_overlay: add an overlay test. add_overlay: add an overlay test.
add_uds_tree: add a UDS test. add_uds_tree: add a UDS test.
add_hostinet: add a hostinet test. add_hostinet: add a hostinet test.
vfs1: enable VFS1 tests. Could be false only if vfs2 is true.
vfs2: enable VFS2 support. vfs2: enable VFS2 support.
fuse: enable FUSE support. fuse: enable FUSE support.
debug: enable debug output. debug: enable debug output.
@ -157,7 +159,7 @@ def syscall_test(
if not tags: if not tags:
tags = [] tags = []
if vfs2 and not fuse: if vfs2 and vfs1 and not fuse:
# Generate a vfs1 plain test. Most testing will now be # Generate a vfs1 plain test. Most testing will now be
# biased towards vfs2, with only a single vfs1 case. # biased towards vfs2, with only a single vfs1 case.
_syscall_test( _syscall_test(
@ -171,7 +173,7 @@ def syscall_test(
**kwargs **kwargs
) )
if not fuse: if vfs1 and not fuse:
# Generate a native test if fuse is not required. # Generate a native test if fuse is not required.
_syscall_test( _syscall_test(
test = test, test = test,

View File

@ -59,7 +59,7 @@ class GetDentsTest : public ::testing::Test {
TEST_F(GetDentsTest, GetDents) { TEST_F(GetDentsTest, GetDents) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
std::vector<std::string> expect = {".", "..", filename_}; std::vector<std::string> expect = {".", "..", filename_};
EXPECT_NO_ERRNO(DirContains(verity_dir, expect, /*exclude=*/{})); EXPECT_NO_ERRNO(DirContains(verity_dir, expect, /*exclude=*/{}));
@ -67,7 +67,7 @@ TEST_F(GetDentsTest, GetDents) {
TEST_F(GetDentsTest, Deleted) { TEST_F(GetDentsTest, Deleted) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
EXPECT_THAT(unlink(JoinPath(tmpfs_dir_.path(), filename_).c_str()), EXPECT_THAT(unlink(JoinPath(tmpfs_dir_.path(), filename_).c_str()),
SyscallSucceeds()); SyscallSucceeds());
@ -78,7 +78,7 @@ TEST_F(GetDentsTest, Deleted) {
TEST_F(GetDentsTest, Renamed) { TEST_F(GetDentsTest, Renamed) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
std::string new_file_name = "renamed-" + filename_; std::string new_file_name = "renamed-" + filename_;
EXPECT_THAT(rename(JoinPath(tmpfs_dir_.path(), filename_).c_str(), EXPECT_THAT(rename(JoinPath(tmpfs_dir_.path(), filename_).c_str(),

View File

@ -106,7 +106,7 @@ TEST_F(IoctlTest, Measure) {
TEST_F(IoctlTest, Mount) { TEST_F(IoctlTest, Mount) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
// Make sure the file can be open and read in the mounted verity fs. // Make sure the file can be open and read in the mounted verity fs.
auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE(
@ -118,7 +118,7 @@ TEST_F(IoctlTest, Mount) {
TEST_F(IoctlTest, NonExistingFile) { TEST_F(IoctlTest, NonExistingFile) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
// Confirm that opening a non-existing file in the verity-enabled directory // Confirm that opening a non-existing file in the verity-enabled directory
// triggers the expected error instead of verification failure. // triggers the expected error instead of verification failure.
@ -129,7 +129,7 @@ TEST_F(IoctlTest, NonExistingFile) {
TEST_F(IoctlTest, ModifiedFile) { TEST_F(IoctlTest, ModifiedFile) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
// Modify the file and check verification failure upon reading from it. // Modify the file and check verification failure upon reading from it.
auto const fd = ASSERT_NO_ERRNO_AND_VALUE( auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
@ -144,7 +144,7 @@ TEST_F(IoctlTest, ModifiedFile) {
TEST_F(IoctlTest, ModifiedMerkle) { TEST_F(IoctlTest, ModifiedMerkle) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
// Modify the Merkle file and check verification failure upon opening the // Modify the Merkle file and check verification failure upon opening the
// corresponding file. // corresponding file.
@ -159,7 +159,7 @@ TEST_F(IoctlTest, ModifiedMerkle) {
TEST_F(IoctlTest, ModifiedDirMerkle) { TEST_F(IoctlTest, ModifiedDirMerkle) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
// Modify the Merkle file for the parent directory and check verification // Modify the Merkle file for the parent directory and check verification
// failure upon opening the corresponding file. // failure upon opening the corresponding file.
@ -174,7 +174,7 @@ TEST_F(IoctlTest, ModifiedDirMerkle) {
TEST_F(IoctlTest, Stat) { TEST_F(IoctlTest, Stat) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
struct stat st; struct stat st;
EXPECT_THAT(stat(JoinPath(verity_dir, filename_).c_str(), &st), EXPECT_THAT(stat(JoinPath(verity_dir, filename_).c_str(), &st),
@ -183,7 +183,7 @@ TEST_F(IoctlTest, Stat) {
TEST_F(IoctlTest, ModifiedStat) { TEST_F(IoctlTest, ModifiedStat) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
EXPECT_THAT(chmod(JoinPath(tmpfs_dir_.path(), filename_).c_str(), 0644), EXPECT_THAT(chmod(JoinPath(tmpfs_dir_.path(), filename_).c_str(), 0644),
SyscallSucceeds()); SyscallSucceeds());
@ -194,7 +194,7 @@ TEST_F(IoctlTest, ModifiedStat) {
TEST_F(IoctlTest, DeleteFile) { TEST_F(IoctlTest, DeleteFile) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
EXPECT_THAT(unlink(JoinPath(tmpfs_dir_.path(), filename_).c_str()), EXPECT_THAT(unlink(JoinPath(tmpfs_dir_.path(), filename_).c_str()),
SyscallSucceeds()); SyscallSucceeds());
@ -204,7 +204,7 @@ TEST_F(IoctlTest, DeleteFile) {
TEST_F(IoctlTest, DeleteMerkle) { TEST_F(IoctlTest, DeleteMerkle) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
EXPECT_THAT( EXPECT_THAT(
unlink(MerklePath(JoinPath(tmpfs_dir_.path(), filename_)).c_str()), unlink(MerklePath(JoinPath(tmpfs_dir_.path(), filename_)).c_str()),
@ -215,7 +215,7 @@ TEST_F(IoctlTest, DeleteMerkle) {
TEST_F(IoctlTest, RenameFile) { TEST_F(IoctlTest, RenameFile) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
std::string new_file_name = "renamed-" + filename_; std::string new_file_name = "renamed-" + filename_;
EXPECT_THAT(rename(JoinPath(tmpfs_dir_.path(), filename_).c_str(), EXPECT_THAT(rename(JoinPath(tmpfs_dir_.path(), filename_).c_str(),
@ -227,7 +227,7 @@ TEST_F(IoctlTest, RenameFile) {
TEST_F(IoctlTest, RenameMerkle) { TEST_F(IoctlTest, RenameMerkle) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
std::string new_file_name = "renamed-" + filename_; std::string new_file_name = "renamed-" + filename_;
EXPECT_THAT( EXPECT_THAT(

View File

@ -58,7 +58,7 @@ class MmapTest : public ::testing::Test {
TEST_F(MmapTest, MmapRead) { TEST_F(MmapTest, MmapRead) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
// Make sure the file can be open and mmapped in the mounted verity fs. // Make sure the file can be open and mmapped in the mounted verity fs.
auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE(
@ -72,7 +72,7 @@ TEST_F(MmapTest, MmapRead) {
TEST_F(MmapTest, ModifiedBeforeMmap) { TEST_F(MmapTest, ModifiedBeforeMmap) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
// Modify the file and check verification failure upon mmapping. // Modify the file and check verification failure upon mmapping.
auto const fd = ASSERT_NO_ERRNO_AND_VALUE( auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
@ -91,7 +91,7 @@ TEST_F(MmapTest, ModifiedBeforeMmap) {
TEST_F(MmapTest, ModifiedAfterMmap) { TEST_F(MmapTest, ModifiedAfterMmap) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE(
Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777)); Open(JoinPath(verity_dir, filename_), O_RDONLY, 0777));
@ -127,7 +127,7 @@ INSTANTIATE_TEST_SUITE_P(
TEST_P(MmapParamTest, Mmap) { TEST_P(MmapParamTest, Mmap) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(
MountVerity(tmpfs_dir_.path(), filename_, /*targets=*/{})); MountVerity(tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY)}));
// Make sure the file can be open and mmapped in the mounted verity fs. // Make sure the file can be open and mmapped in the mounted verity fs.
auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE( auto const verity_fd = ASSERT_NO_ERRNO_AND_VALUE(

View File

@ -62,9 +62,9 @@ class SymlinkTest : public ::testing::Test {
}; };
TEST_F(SymlinkTest, Success) { TEST_F(SymlinkTest, Success) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(MountVerity(
MountVerity(tmpfs_dir_.path(), filename_, tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY),
{EnableTarget(kSymlink, O_RDONLY | O_NOFOLLOW)})); EnableTarget(kSymlink, O_RDONLY | O_NOFOLLOW)}));
char buf[256]; char buf[256];
EXPECT_THAT( EXPECT_THAT(
@ -77,9 +77,9 @@ TEST_F(SymlinkTest, Success) {
} }
TEST_F(SymlinkTest, DeleteLink) { TEST_F(SymlinkTest, DeleteLink) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(MountVerity(
MountVerity(tmpfs_dir_.path(), filename_, tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY),
{EnableTarget(kSymlink, O_RDONLY | O_NOFOLLOW)})); EnableTarget(kSymlink, O_RDONLY | O_NOFOLLOW)}));
ASSERT_THAT(unlink(JoinPath(tmpfs_dir_.path(), kSymlink).c_str()), ASSERT_THAT(unlink(JoinPath(tmpfs_dir_.path(), kSymlink).c_str()),
SyscallSucceeds()); SyscallSucceeds());
@ -92,9 +92,9 @@ TEST_F(SymlinkTest, DeleteLink) {
} }
TEST_F(SymlinkTest, ModifyLink) { TEST_F(SymlinkTest, ModifyLink) {
std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE( std::string verity_dir = ASSERT_NO_ERRNO_AND_VALUE(MountVerity(
MountVerity(tmpfs_dir_.path(), filename_, tmpfs_dir_.path(), {EnableTarget(filename_, O_RDONLY),
{EnableTarget(kSymlink, O_RDONLY | O_NOFOLLOW)})); EnableTarget(kSymlink, O_RDONLY | O_NOFOLLOW)}));
ASSERT_THAT(unlink(JoinPath(tmpfs_dir_.path(), kSymlink).c_str()), ASSERT_THAT(unlink(JoinPath(tmpfs_dir_.path(), kSymlink).c_str()),
SyscallSucceeds()); SyscallSucceeds());

View File

@ -54,20 +54,14 @@ PosixError FlipRandomBit(int fd, int size) {
return NoError(); return NoError();
} }
PosixErrorOr<std::string> MountVerity(std::string tmpfs_dir, PosixErrorOr<std::string> MountVerity(std::string lower_dir,
std::string filename,
std::vector<EnableTarget> targets) { std::vector<EnableTarget> targets) {
// Mount a verity fs on the existing tmpfs mount. // Mount a verity fs on the existing mount.
std::string mount_opts = "lower_path=" + tmpfs_dir; std::string mount_opts = "lower_path=" + lower_dir;
ASSIGN_OR_RETURN_ERRNO(TempPath verity_dir, TempPath::CreateDir()); ASSIGN_OR_RETURN_ERRNO(TempPath verity_dir, TempPath::CreateDir());
RETURN_ERROR_IF_SYSCALL_FAIL( RETURN_ERROR_IF_SYSCALL_FAIL(
mount("", verity_dir.path().c_str(), "verity", 0, mount_opts.c_str())); mount("", verity_dir.path().c_str(), "verity", 0, mount_opts.c_str()));
// Enable the file, symlink(if provided) and the directory.
ASSIGN_OR_RETURN_ERRNO(
auto fd, Open(JoinPath(verity_dir.path(), filename), O_RDONLY, 0777));
RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(fd.get(), FS_IOC_ENABLE_VERITY));
for (const EnableTarget& target : targets) { for (const EnableTarget& target : targets) {
ASSIGN_OR_RETURN_ERRNO( ASSIGN_OR_RETURN_ERRNO(
auto target_fd, auto target_fd,
@ -92,6 +86,7 @@ PosixErrorOr<std::string> MountVerity(std::string tmpfs_dir,
ASSIGN_OR_RETURN_ERRNO(TempPath verity_with_hash_dir, TempPath::CreateDir()); ASSIGN_OR_RETURN_ERRNO(TempPath verity_with_hash_dir, TempPath::CreateDir());
RETURN_ERROR_IF_SYSCALL_FAIL(mount("", verity_with_hash_dir.path().c_str(), RETURN_ERROR_IF_SYSCALL_FAIL(mount("", verity_with_hash_dir.path().c_str(),
"verity", 0, mount_opts.c_str())); "verity", 0, mount_opts.c_str()));
// Verity directories should not be deleted. Release the TempPath objects to // Verity directories should not be deleted. Release the TempPath objects to
// prevent those directories from being deleted by the destructor. // prevent those directories from being deleted by the destructor.
verity_dir.release(); verity_dir.release();

View File

@ -76,7 +76,6 @@ PosixError FlipRandomBit(int fd, int size);
// Mount a verity on the tmpfs and enable both the file and the direcotry. Then // Mount a verity on the tmpfs and enable both the file and the direcotry. Then
// mount a new verity with measured root hash. // mount a new verity with measured root hash.
PosixErrorOr<std::string> MountVerity(std::string tmpfs_dir, PosixErrorOr<std::string> MountVerity(std::string tmpfs_dir,
std::string filename,
std::vector<EnableTarget> targets); std::vector<EnableTarget> targets);
} // namespace testing } // namespace testing