blob: a26aa00ab9299a41c6ac5ce09ad2008c082efc53 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/sys/fuzzing/framework/engine/corpus.h"
#include <unordered_set>
#include <gtest/gtest.h>
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
namespace fuzzing {
namespace {
// Test fixtures.
Input input0() { return Input(); }
Input input1() { return Input({0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48}); }
Input input2() { return Input({0x21, 0x22}); }
Input input3() { return Input({0x31, 0x32, 0x33, 0x34, 0x35, 0x36}); }
Input input4() { return Input({0x41, 0x42, 0x43, 0x44}); }
OptionsPtr DefaultOptions() {
auto options = MakeOptions();
Corpus::AddDefaults(options.get());
return options;
}
void AddAllToCorpus(Corpus *corpus) {
ASSERT_EQ(corpus->Add(input1()), ZX_OK);
ASSERT_EQ(corpus->Add(input2()), ZX_OK);
ASSERT_EQ(corpus->Add(input3()), ZX_OK);
ASSERT_EQ(corpus->Add(input4()), ZX_OK);
}
// Unit tests.
TEST(CorpusTest, AddDefaults) {
Options options;
Corpus::AddDefaults(&options);
EXPECT_EQ(options.seed(), kDefaultSeed);
EXPECT_EQ(options.max_input_size(), kDefaultMaxInputSize);
}
TEST(CorpusTest, Load) {
Corpus corpus;
corpus.Configure(DefaultOptions());
// Double check that the unit test component manifest includes the tmp-storage shard...
ASSERT_TRUE(files::IsDirectory("/tmp"));
// Create a hierarchy of temporary directories. The unusual names are chosen to make the
// structure more apparent to the reader of this file.
auto r______ = files::JoinPath("/tmp", "corpus-test");
auto d_1____ = files::JoinPath(r______, "1");
auto d_1_1__ = files::JoinPath(d_1____, "1");
auto d_1_2__ = files::JoinPath(d_1____, "2");
auto d_2____ = files::JoinPath(r______, "2");
auto d_2_1__ = files::JoinPath(d_2____, "1");
auto d_2_2__ = files::JoinPath(d_2____, "2");
auto d_2_1_1 = files::JoinPath(d_2_1__, "1");
// |CreateDirectory| automatically creates parents, so it only needs to be called on the leaves.
EXPECT_TRUE(files::CreateDirectory(d_1_1__));
EXPECT_TRUE(files::CreateDirectory(d_1_2__));
EXPECT_TRUE(files::CreateDirectory(d_2_1_1));
EXPECT_TRUE(files::CreateDirectory(d_2_2__));
// Create some files to include conditions like:
// * a directory with more than one file (d_1_1__).
// * a directory with exactly one file (d_1_2__)
// * a directory with only nested files (d_2_1__)
// * a directory with no files (d_2_2__)
EXPECT_TRUE(files::WriteFile(files::JoinPath(d_1_1__, "1"), "foo"));
EXPECT_TRUE(files::WriteFile(files::JoinPath(d_1_1__, "2"), "bar"));
EXPECT_TRUE(files::WriteFile(files::JoinPath(d_1_2__, "1"), "baz"));
EXPECT_TRUE(files::WriteFile(files::JoinPath(d_2_1_1, "1"), "qux"));
EXPECT_EQ(corpus.LoadAt(r______, {"1", "2"}), ZX_OK);
Input inputs[5];
for (size_t i = 0; i < 4; ++i) {
EXPECT_TRUE(corpus.At(i + 1, &inputs[i]));
}
EXPECT_FALSE(corpus.At(5, &inputs[4]));
// Should be sorted on return.
EXPECT_EQ(inputs[0], Input("bar"));
EXPECT_EQ(inputs[1], Input("baz"));
EXPECT_EQ(inputs[2], Input("foo"));
EXPECT_EQ(inputs[3], Input("qux"));
}
TEST(CorpusTest, AddInputs) {
Corpus corpus;
auto options = DefaultOptions();
options->set_max_input_size(8);
corpus.Configure(options);
// Empty input is implicitly included.
EXPECT_EQ(corpus.num_inputs(), 1U);
EXPECT_EQ(corpus.total_size(), 0U);
size_t expected = input1().size() + input2().size();
EXPECT_EQ(corpus.Add(input1()), ZX_OK);
EXPECT_EQ(corpus.Add(input2()), ZX_OK);
EXPECT_EQ(corpus.num_inputs(), 3U);
EXPECT_EQ(corpus.total_size(), expected);
// Empty inputs are not added.
EXPECT_EQ(corpus.Add(input0()), ZX_OK);
EXPECT_EQ(corpus.num_inputs(), 3U);
EXPECT_EQ(corpus.total_size(), expected);
// Over-large inputs return an error.
Input large_input;
large_input.Resize(9);
EXPECT_EQ(corpus.Add(std::move(large_input)), ZX_ERR_BUFFER_TOO_SMALL);
EXPECT_EQ(corpus.num_inputs(), 3U);
EXPECT_EQ(corpus.total_size(), expected);
// Adding an existing input doesn't change the number of inputs or total size.
EXPECT_EQ(corpus.Add(input1()), ZX_OK);
EXPECT_EQ(corpus.Add(input2()), ZX_OK);
EXPECT_EQ(corpus.num_inputs(), 3U);
EXPECT_EQ(corpus.total_size(), expected);
}
TEST(CorpusTest, AddCorpus) {
auto corpus1 = Corpus::MakePtr();
auto options = DefaultOptions();
options->set_max_input_size(4);
corpus1->Configure(options);
EXPECT_EQ(corpus1->Add(Input("foo")), ZX_OK);
EXPECT_EQ(corpus1->Add(Input("bar")), ZX_OK);
// Null pointer.
CorpusPtr corpus2;
EXPECT_EQ(corpus1->Add(corpus2), ZX_ERR_INVALID_ARGS);
// Long input.
corpus2 = Corpus::MakePtr();
corpus2->Configure(DefaultOptions());
EXPECT_EQ(corpus2->Add(Input("foobarbaz")), ZX_OK);
EXPECT_EQ(corpus1->Add(corpus2), ZX_ERR_BUFFER_TOO_SMALL);
// Valid
corpus2 = Corpus::MakePtr();
corpus2->Configure(DefaultOptions());
EXPECT_EQ(corpus2->Add(Input("bar")), ZX_OK);
EXPECT_EQ(corpus2->Add(Input("baz")), ZX_OK);
EXPECT_EQ(corpus1->Add(corpus2), ZX_OK);
Input inputs[4];
for (size_t i = 0; i < 3; ++i) {
EXPECT_TRUE(corpus1->At(i + 1, &inputs[i]));
}
EXPECT_FALSE(corpus1->At(4, &inputs[3]));
// Should be sorted on return.
EXPECT_EQ(inputs[0], Input("bar"));
EXPECT_EQ(inputs[1], Input("baz"));
EXPECT_EQ(inputs[2], Input("foo"));
}
TEST(CorpusTest, At) {
Corpus corpus;
corpus.Configure(DefaultOptions());
// Empty input is always present.
Input input;
EXPECT_TRUE(corpus.At(0, &input));
EXPECT_EQ(input.ToHex(), input0().ToHex());
// Add some elements.
AddAllToCorpus(&corpus);
// Corpus should been in sorted order: shortest to longest.
EXPECT_TRUE(corpus.At(0, &input));
EXPECT_EQ(input.ToHex(), input0().ToHex());
EXPECT_TRUE(corpus.At(1, &input));
EXPECT_EQ(input.ToHex(), input2().ToHex());
EXPECT_TRUE(corpus.At(2, &input));
EXPECT_EQ(input.ToHex(), input4().ToHex());
EXPECT_TRUE(corpus.At(3, &input));
EXPECT_EQ(input.ToHex(), input3().ToHex());
EXPECT_TRUE(corpus.At(4, &input));
EXPECT_EQ(input.ToHex(), input1().ToHex());
// Out-of-bounds returns empty input.
EXPECT_FALSE(corpus.At(5, &input));
EXPECT_EQ(input.ToHex(), input0().ToHex());
}
TEST(CorpusTest, Pick) {
Corpus corpus;
// Set the seed explicitly. In the real system, omitting the seed option will cause the engine to
// derive one from the current time.
auto options = DefaultOptions();
options->set_seed(100);
corpus.Configure(options);
// Corpus always has an empty input.
Input input;
corpus.Pick(&input);
EXPECT_EQ(input.ToHex(), input0().ToHex());
// |Pick| doesn't exhaust, but does shuffle.
AddAllToCorpus(&corpus);
std::vector<std::string> ordered_a;
for (size_t i = 0; i < 100; ++i) {
corpus.Pick(&input);
ordered_a.push_back(input.ToHex());
}
std::vector<std::string> ordered_b;
for (size_t i = 0; i < 100; ++i) {
corpus.Pick(&input);
ordered_b.push_back(input.ToHex());
}
std::unordered_set<std::string> unique_a(ordered_a.begin(), ordered_a.end());
std::unordered_set<std::string> unique_b(ordered_b.begin(), ordered_b.end());
// The loop above should pick all inputs, but in different order. These assertions are very likely
// but not guaranteed for an arbitrary seed. For the given seed, they work.
EXPECT_EQ(unique_a.size(), corpus.num_inputs());
EXPECT_EQ(unique_b.size(), corpus.num_inputs());
EXPECT_NE(ordered_a, ordered_b);
}
TEST(CorpusTest, PickIsDeterministic) {
Corpus corpus1;
Corpus corpus2;
// Set the seed explicitly. In the real system, omitting the seed option will cause the engine to
// derive one from the current time.
auto options = DefaultOptions();
options->set_seed(100);
corpus1.Configure(options);
corpus2.Configure(options);
// Same seed and inputs should produce same order.
AddAllToCorpus(&corpus1);
AddAllToCorpus(&corpus2);
Input input1;
Input input2;
for (size_t i = 0; i < 100; ++i) {
corpus1.Pick(&input1);
corpus2.Pick(&input2);
EXPECT_EQ(input1.ToHex(), input2.ToHex());
}
}
} // namespace
} // namespace fuzzing