blob: ea0734f6e81c8755c38c3a5c2ae8f41e00755a8e [file] [log] [blame]
// Copyright 2021 Google LLC
//
// 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 "util.hpp"
#include <flasher/mutate/asymmetric.hpp>
#include <flasher/ops.hpp>
#include <cstring>
#include <memory>
#include <optional>
#include <stdexcept>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace flasher
{
namespace ops
{
using testing::ElementsAre;
using testing::SizeIs;
class AutoTest : public OpTest<testing::NiceMock<MockOrFakeDevice>>
{
protected:
AutoTest()
{
fillFileInc(f, 0, 12);
}
};
TEST_F(AutoTest, InvalidOffset)
{
EXPECT_THROW(automatic(d, /*dev_offset=*/13, f, /*file_offset=*/0, m,
/*max_size=*/0, /*stride_size=*/std::nullopt,
/*noread=*/false),
std::invalid_argument);
}
TEST_F(AutoTest, InvalidStride)
{
EXPECT_THROW(automatic(d, /*dev_offset=*/0, f, /*file_offset=*/0, m,
/*max_size=*/0, /*stride_size=*/0, /*noread=*/false),
std::invalid_argument);
}
TEST_F(AutoTest, SimpleOverwrite)
{
memset(f.data.data(), 0, f.data.size());
EXPECT_NE(f.data, df.data);
automatic(d, /*dev_offset=*/0, f, /*file_offset=*/0, m, /*max_size=*/20,
/*stride_size=*/4, /*noread=*/false);
EXPECT_EQ(f.data, df.data);
}
TEST_F(AutoTest, SimpleOverwriteNoRead)
{
memset(f.data.data(), 0, f.data.size());
EXPECT_NE(f.data, df.data);
automatic(d, /*dev_offset=*/0, f, /*file_offset=*/0, m, /*max_size=*/20,
/*stride_size=*/4, /*noread=*/true);
EXPECT_EQ(f.data, df.data);
}
TEST_F(AutoTest, SmallFile)
{
f.data.resize(4);
memset(f.data.data(), 1, f.data.size());
automatic(d, /*dev_offset=*/0, f, /*file_offset=*/0, m, /*max_size=*/20,
/*stride_size=*/std::nullopt, /*noread=*/false);
df.data.resize(4);
EXPECT_EQ(f.data, df.data);
}
TEST_F(AutoTest, SmallFileWithSimpleDev)
{
testing::NiceMock<MockOrFakeDevice> simpleDev(fillFileInc(df, 0, 12),
Device::Type::Simple, 0);
f.data.resize(4);
memset(f.data.data(), 2, f.data.size());
automatic(d, /*dev_offset=*/0, f, /*file_offset=*/0, m, /*max_size=*/20,
/*stride_size=*/std::nullopt, /*noread=*/false);
df.data.resize(4);
EXPECT_EQ(f.data, df.data);
}
TEST_F(AutoTest, FileTooBig)
{
fillFileInc(f, 0, 13);
EXPECT_THROW(automatic(d, /*dev_offset=*/0, f, /*file_offset=*/0, m,
/*max_size=*/20, /*stride_size=*/std::nullopt,
/*noread=*/false),
std::runtime_error);
EXPECT_THROW(automatic(d, /*dev_offset=*/0, f, /*file_offset=*/0, m,
/*max_size=*/20, /*stride_size=*/std::nullopt,
/*noread=*/true),
std::runtime_error);
}
TEST_F(AutoTest, ValidSubset)
{
fillFileInc(f, 2, 10);
memset(df.data.data(), 1, df.data.size());
automatic(d, /*dev_offset=*/3, f, /*file_offset=*/1, m, /*max_size=*/5,
/*stride_size=*/std::nullopt, /*noread=*/false);
EXPECT_THAT(df.data, ElementsAre(1_b, 1_b, 1_b, 3_b, 4_b, 5_b, 6_b, 7_b,
1_b, 1_b, 1_b, 1_b));
}
TEST_F(AutoTest, ValidSubsetNoRead)
{
fillFileInc(f, 2, 10);
memset(df.data.data(), 1, df.data.size());
automatic(d, /*dev_offset=*/3, f, /*file_offset=*/1, m, /*max_size=*/5,
/*stride_size=*/3, /*noread=*/true);
EXPECT_THAT(df.data, ElementsAre(1_b, 1_b, 1_b, 3_b, 4_b, 5_b, 6_b, 7_b,
1_b, 1_b, 1_b, 1_b));
}
TEST_F(AutoTest, TooBigSubset)
{
fillFileInc(f, 2, 11);
EXPECT_THROW(automatic(d, /*dev_offset=*/3, f, /*file_offset=*/1, m,
/*max_size=*/20, /*stride_size=*/std::nullopt,
/*noread=*/false),
std::runtime_error);
EXPECT_THROW(automatic(d, /*dev_offset=*/3, f, /*file_offset=*/1, m,
/*max_size=*/20, /*stride_size=*/std::nullopt,
/*noread=*/true),
std::runtime_error);
}
TEST_F(AutoTest, MutateApplied)
{
m.mutations.push_back(std::make_unique<mutate::Asymmetric>());
memset(f.data.data(), 0, f.data.size());
memset(df.data.data(), 9, df.data.size());
automatic(d, /*dev_offset=*/1, f, /*file_offset=*/1, m, /*max_size=*/4,
/*stride_size=*/std::nullopt, /*noread=*/false);
EXPECT_THAT(df.data, ElementsAre(9_b, 1_b, 2_b, 3_b, 4_b, 9_b, 9_b, 9_b,
9_b, 9_b, 9_b, 9_b));
}
TEST_F(AutoTest, OnlyNeededOps)
{
f.data = {3_b, 3_b, 3_b, 3_b, 3_b, 3_b, 3_b, 3_b};
df.data = {3_b, 1_b, 2_b, 3_b, 3_b, 3_b, 7_b, 3_b};
testing::Sequence seq;
EXPECT_CALL(d, eraseBlocks(0, 1)).InSequence(seq);
EXPECT_CALL(d, writeAt(SizeIs(4), 0)).InSequence(seq);
EXPECT_CALL(d, writeAt(SizeIs(1), 6));
automatic(d, /*dev_offset=*/0, f, /*file_offset=*/0, m, /*max_size=*/8,
/*stride_size=*/4, /*noread=*/false);
EXPECT_EQ(f.data, df.data);
}
// Add a test to cover a corner case hit previously.
// The setup is with binary size of 0x10 and erase size of 4. This is the
// smallest test
TEST_F(AutoTest, CornerCase)
{
size_t eraseSize = 4;
size_t size = 0x10;
file::Memory df, f;
NestedMutate m;
testing::NiceMock<MockOrFakeDevice> d(fillFileInc(df, 0, size),
Device::Type::Nor, eraseSize);
f.data.resize(size);
// Binary snippet that hit a corner case. with 0xb0 binary and 8 erase size.
// {0xff_b, 0xff_b, 0x00_b, 0x00_b, 0xff_b, 0xff_b, 0xff_b, 0xff_b, 0xc6_b,
// 0x00_b, 0x39_b, 0x00_b, 0x48_b, 0x00_b, 0x0e_b, 0x00_b, 0x73_b, 0x00_b,
// 0x14_b, 0x00_b, 0xc7_b, 0x00_b, 0x3a_b, 0x00_b, 0xc3_b, 0x00_b, 0x36_b,
// 0x00_b, 0xcb_b, 0x00_b, 0x03_b, 0x00_b, 0xc0_b, 0x00_b, 0x76_b, 0x00_b,
// 0xc4_b, 0x00_b, 0x37_b, 0x00_b, 0xc9_b, 0x00_b, 0x15_b, 0x00_b, 0x44_b,
// 0x00_b, 0x0d_b, 0x00_b, 0x75_b, 0x00_b, 0x79_b, 0x00_b, 0x74_b, 0x00_b,
// 0x7a_b, 0x00_b, 0xd7_b, 0x00_b, 0x13_b, 0x00_b, 0x4c_b, 0x00_b, 0x0f_b,
// 0x00_b, 0x40_b, 0x00_b, 0x0c_b, 0x00_b, 0xcf_b, 0x00_b, 0x11_b, 0x00_b,
// 0xc5_b, 0x00_b, 0x38_b, 0x00_b, 0x88_b, 0x00_b, 0x16_b, 0x00_b, 0xc1_b,
// 0x00_b, 0x77_b, 0x00_b, 0x89_b, 0x00_b, 0x78_b, 0x00_b, 0xa5_b, 0x00_b,
// 0x1c_b, 0x00_b, 0xf5_b, 0x00_b, 0x2b_b, 0x00_b, 0xa6_b, 0x00_b, 0x1b_b,
// 0x00_b, 0xa4_b, 0x00_b, 0x1d_b, 0x00_b, 0xca_b, 0x00_b, 0x17_b, 0x00_b,
// 0x70_b, 0x00_b, 0x05_b, 0x00_b, 0x08_b, 0xf0_b, 0x02_b, 0x80_b, 0x00_b,
// 0x00_b, 0x00_b, 0x01_b, 0x44_b, 0x00_b, 0x03_b, 0x00_b, 0x00_b, 0x00_b,
// 0x00_b, 0x00_b, 0x08_b, 0xf6_b, 0x02_b, 0x80_b, 0x00_b, 0x00_b, 0x00_b,
// 0x01_b, 0x5c_b, 0x00_b, 0x03_b, 0x1e_b, 0x00_b, 0x00_b, 0x00_b, 0x00_b,
// 0x09_b, 0x40_b, 0x00_b, 0x00_b, 0x00_b, 0x00_b, 0x00_b, 0x00_b, 0x00_b,
// 0x00_b, 0x00_b, 0x00_b, 0x00_b, 0x00_b, 0x00_b, 0x00_b, 0x00_b, 0x00_b,
// 0x00_b, 0x00_b, 0x12_b, 0x00_b, 0x00_b, 0x00_b, 0x00_b, 0x00_b, 0x00_b,
// 0x00_b, 0x00_b, 0x00_b, 0x00_b, 0x00_b};
for (size_t i = 0; i < size / eraseSize; ++i)
{
std::fill(f.data.begin(), f.data.end(), 0_b);
// The main failures is caused by 0xff at the beginning of the erase
// block.
f.data[eraseSize * i] = 0xff_b;
// The index that will triggered the failure is not exactly clear.
// Most index after eraseSize + 1 will trigger the failure.
f.data[size - 1] = 0x12_b;
automatic(d, /*dev_offset=*/0, f, /*file_offset=*/0, m,
/*max_size=*/std::numeric_limits<size_t>::max(),
/*stride_size=*/std::nullopt, /*noread=*/false);
EXPECT_EQ(f.data, df.data);
}
}
TEST_F(AutoTest, ReachEnd)
{
fillFileInc(f, 0, 12);
automatic(d, /*dev_offset=*/0, f, /*file_offset=*/0, m, /*max_size=*/64,
/*stride_size=*/4, /*noread=*/false);
}
} // namespace ops
} // namespace flasher