blob: e46e7eac915bed4043b104c32e7d073a64fbfc8a [file] [log] [blame]
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 "syzygy/bard/story.h"
#include <vector>
#include "base/atomicops.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "syzygy/bard/events/heap_alloc_event.h"
#include "syzygy/bard/events/heap_create_event.h"
#include "syzygy/bard/events/heap_destroy_event.h"
#include "syzygy/bard/events/heap_free_event.h"
#include "syzygy/bard/events/heap_size_event.h"
#include "syzygy/bard/events/linked_event.h"
#include "syzygy/core/unittest_util.h"
namespace bard {
namespace {
using events::LinkedEvent;
using events::HeapAllocEvent;
using events::HeapCreateEvent;
using events::HeapDestroyEvent;
using events::HeapFreeEvent;
using events::HeapSizeEvent;
const HANDLE kLiveHeap = reinterpret_cast<HANDLE>(0x4197FC83);
const HANDLE kTraceHeap = reinterpret_cast<HANDLE>(0xAB12CD34);
const LPVOID kLiveAlloc = reinterpret_cast<LPVOID>(0x4820BC7A);
const LPVOID kTraceAlloc = reinterpret_cast<LPVOID>(0xF1D97AE4);
const DWORD kFlags = 1;
const DWORD kOptions = 0;
const SIZE_T kBytes = 100;
const SIZE_T kSize = 100;
const SIZE_T kInitialSize = 1;
const SIZE_T kMaximumSize = 1000;
class InvalidEvent : public EventInterface {
public:
EventType type() const override { return EventType::kMaxEventType; }
bool Play(void* backdrop) override { return true; }
bool Equals(const EventInterface*) const override { return false; }
};
// A simple event that simply returns false. Used for testing playback.
class FailedEvent : public EventInterface {
public:
EventType type() const override { return EventType::kMaxEventType; }
bool Play(void* backdrop) override { return false; }
bool Equals(const EventInterface*) const override { return false; }
};
// A simple event that appends its ID to a vector. Used for testing playback.
class AppendEvent : public EventInterface {
public:
explicit AppendEvent(uint32_t id) : id_(id) {}
EventType type() const override { return EventType::kMaxEventType; }
bool Play(void* backdrop) override {
auto v = reinterpret_cast<std::vector<uint32_t>*>(backdrop);
v->push_back(id_);
return true;
}
bool Equals(const EventInterface*) const override { return false; }
private:
uint32_t id_;
};
// A simple event that increments a counter atomically. Used for testing
// playback.
class IncrementEvent : public EventInterface {
public:
explicit IncrementEvent(uint32_t amount) : amount_(amount) {}
EventType type() const override { return EventType::kMaxEventType; }
bool Play(void* backdrop) override {
auto atomic = reinterpret_cast<volatile base::subtle::Atomic32*>(backdrop);
base::subtle::Barrier_AtomicIncrement(atomic, amount_);
return true;
}
bool Equals(const EventInterface*) const override { return false; }
private:
uint32_t amount_;
};
} // namespace
TEST(StoryTest, CreatePlotLine) {
Story s;
EXPECT_EQ(0u, s.plot_lines().size());
auto pl = s.CreatePlotLine();
EXPECT_TRUE(pl);
EXPECT_EQ(1u, s.plot_lines().size());
Story::PlotLine* pl2 = new Story::PlotLine();
EXPECT_EQ(pl2, s.AddPlotLine(std::unique_ptr<Story::PlotLine>(pl2)));
EXPECT_EQ(2u, s.plot_lines().size());
}
TEST(StoryTest, TestSerialization) {
std::unique_ptr<EventInterface> event1(
new HeapCreateEvent(0, kOptions, kInitialSize, kMaximumSize, kTraceHeap));
std::unique_ptr<EventInterface> event2(
new HeapAllocEvent(0, kTraceHeap, kFlags, kBytes, kTraceAlloc));
std::unique_ptr<EventInterface> event3(
new HeapSizeEvent(0, kTraceHeap, kFlags, kTraceAlloc, kSize));
std::unique_ptr<EventInterface> event4(
new HeapFreeEvent(0, kTraceHeap, kFlags, kTraceAlloc, true));
std::unique_ptr<EventInterface> event5(
new HeapDestroyEvent(0, kTraceHeap, true));
// The following events will either be cross plot line dependencies or have
// such dependencies.
std::unique_ptr<LinkedEvent> linked_event1(
new LinkedEvent(std::move(event1)));
std::unique_ptr<LinkedEvent> linked_event2(
new LinkedEvent(std::move(event2)));
std::unique_ptr<LinkedEvent> linked_event4(
new LinkedEvent(std::move(event4)));
std::unique_ptr<LinkedEvent> linked_event5(
new LinkedEvent(std::move(event5)));
// Alloc depends on Create, as it would be on another thread.
linked_event2->AddDep(linked_event1.get());
// Similarly the heap can't be detroyed until all use of it has been
// completed.
linked_event5->AddDep(linked_event4.get());
std::unique_ptr<Story::PlotLine> plot_line1(new Story::PlotLine());
std::unique_ptr<Story::PlotLine> plot_line2(new Story::PlotLine());
// One plot line creates and frees the heap.
plot_line1->push_back(linked_event1.release());
plot_line1->push_back(linked_event5.release());
// Another plot line owns the allocation.
plot_line2->push_back(linked_event2.release());
plot_line2->push_back(event3.release());
plot_line2->push_back(linked_event4.release());
// Create a story to wrap it all up.
Story story;
story.AddPlotLine(std::move(plot_line1));
story.AddPlotLine(std::move(plot_line2));
EXPECT_TRUE(testing::TestSerialization(story));
}
TEST(PlotLineRunnerTest, StopOnFailedEvent) {
Story::PlotLine plot_line;
plot_line.push_back(new AppendEvent(0));
plot_line.push_back(new FailedEvent());
plot_line.push_back(new AppendEvent(1));
std::vector<uint32_t> v;
Story::PlotLineRunner runner(&v, &plot_line);
runner.Start();
runner.Join();
EXPECT_TRUE(runner.Failed());
EXPECT_EQ(plot_line[1], runner.failed_event());
EXPECT_THAT(v, testing::ElementsAre(0));
}
TEST(PlotLineRunnerTest, Succeeds) {
Story::PlotLine plot_line;
plot_line.push_back(new AppendEvent(0));
plot_line.push_back(new AppendEvent(1));
plot_line.push_back(new AppendEvent(2));
std::vector<uint32_t> v;
Story::PlotLineRunner runner(&v, &plot_line);
runner.Start();
runner.Join();
EXPECT_FALSE(runner.Failed());
EXPECT_EQ(nullptr, runner.failed_event());
EXPECT_THAT(v, testing::ElementsAre(0, 1, 2));
}
TEST(StoryTest, PlaybackStopsAndFails) {
Story story;
auto plot_line = story.CreatePlotLine();
plot_line->push_back(new AppendEvent(0));
plot_line->push_back(new FailedEvent());
plot_line->push_back(new AppendEvent(1));
std::vector<uint32_t> v;
EXPECT_FALSE(story.Play(&v));
EXPECT_THAT(v, testing::ElementsAre(0));
}
TEST(StoryTest, PlaybackSucceeds) {
Story story;
// 10 plotlines (threads) with 10000 events each was sufficient to generate
// race conditions on a Z600.
uint32_t sum = 0;
for (size_t i = 0; i < 10; ++i) {
auto pl = story.CreatePlotLine();;
for (size_t j = 0; j < 10000; ++j) {
pl->push_back(new IncrementEvent(j + i));
sum += j + i;
}
}
base::subtle::Atomic32 atomic = 0;
EXPECT_TRUE(story.Play(&atomic));
EXPECT_EQ(sum, atomic);
}
} // namespace bard