v8/test/cctest/cctest.cc
Leszek Swirski 49c507dc99 [test] Make cctest run one test, with maybe custom platform
Remove cctest's ability to run multiple tests (which has long been
deprecated and mostly broken). We can then make platform & V8
initialisation be part of running the test's Run method.

In particular, this allows us to inject custom logic into the platform
initialisation, like setting up a platform wrapper. Add a
TEST_WITH_PLATFORM which exercises this by registering a platform
factory on the test, and wrapping the default platform using this
factory. This allows these tests to guarantee that the lifetime of the
platform is longer than the lifetime of the isolate.

As a result of this, we can also remove the complexity around draining
platform state in the TestPlatform (since it will now have a longer
lifetime than the Isolate using it), and as a drive-by clean up the
TestPlaform to use a CcTest-global "default platform" instead of trying
to scope over the "current" platform.

As another drive-by, change the linked-list of CcTests and the linear
search through it into an std::map of tests.

Change-Id: I610f6312fe042f29f45cc4dfba311e4184bc7759
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3569223
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79772}
2022-04-05 09:39:18 +00:00

548 lines
20 KiB
C++

// Copyright 2008 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "test/cctest/cctest.h"
#include "include/cppgc/platform.h"
#include "include/libplatform/libplatform.h"
#include "include/v8-array-buffer.h"
#include "include/v8-context.h"
#include "include/v8-function.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-locker.h"
#include "src/base/lazy-instance.h"
#include "src/base/logging.h"
#include "src/base/platform/condition-variable.h"
#include "src/base/platform/mutex.h"
#include "src/base/platform/semaphore.h"
#include "src/base/strings.h"
#include "src/codegen/compiler.h"
#include "src/codegen/optimized-compilation-info.h"
#include "src/common/globals.h"
#include "src/compiler/pipeline.h"
#include "src/debug/debug.h"
#include "src/flags/flags.h"
#include "src/objects/objects-inl.h"
#include "src/trap-handler/trap-handler.h"
#include "test/cctest/print-extension.h"
#include "test/cctest/profiler-extension.h"
#include "test/cctest/trace-extension.h"
#ifdef V8_USE_PERFETTO
#include "src/tracing/trace-event.h"
#endif // V8_USE_PERFETTO
#if V8_OS_WIN
#include <windows.h>
#if V8_CC_MSVC
#include <crtdbg.h>
#endif
#endif
enum InitializationState { kUnset, kUninitialized, kInitialized };
static InitializationState initialization_state_ = kUnset;
static v8::base::LazyInstance<CcTestMapType>::type g_cctests =
LAZY_INSTANCE_INITIALIZER;
std::unordered_map<std::string, CcTest*>* tests_ =
new std::unordered_map<std::string, CcTest*>();
bool CcTest::initialize_called_ = false;
v8::base::Atomic32 CcTest::isolate_used_ = 0;
v8::ArrayBuffer::Allocator* CcTest::allocator_ = nullptr;
v8::Isolate* CcTest::isolate_ = nullptr;
v8::Platform* CcTest::default_platform_ = nullptr;
CcTest::CcTest(TestFunction* callback, const char* file, const char* name,
bool enabled, bool initialize,
TestPlatformFactory* test_platform_factory)
: callback_(callback),
initialize_(initialize),
test_platform_factory_(test_platform_factory) {
// Find the base name of this test (const_cast required on Windows).
char *basename = strrchr(const_cast<char *>(file), '/');
if (!basename) {
basename = strrchr(const_cast<char *>(file), '\\');
}
if (!basename) {
basename = v8::internal::StrDup(file);
} else {
basename = v8::internal::StrDup(basename + 1);
}
// Drop the extension, if there is one.
char *extension = strrchr(basename, '.');
if (extension) *extension = 0;
// Install this test in the list of tests
if (enabled) {
auto it =
g_cctests.Pointer()->emplace(std::string(basename) + "/" + name, this);
CHECK_WITH_MSG(it.second, "Test with same name already exists");
}
v8::internal::DeleteArray(basename);
}
void CcTest::Run(const char* snapshot_directory) {
v8::V8::InitializeICUDefaultLocation(snapshot_directory);
std::unique_ptr<v8::Platform> underlying_default_platform(
v8::platform::NewDefaultPlatform());
default_platform_ = underlying_default_platform.get();
std::unique_ptr<v8::Platform> platform;
if (test_platform_factory_) {
platform = test_platform_factory_();
} else {
platform = std::move(underlying_default_platform);
}
v8::V8::InitializePlatform(platform.get());
#ifdef V8_SANDBOX
CHECK(v8::V8::InitializeSandbox());
#endif
cppgc::InitializeProcess(platform->GetPageAllocator());
v8::V8::Initialize();
v8::V8::InitializeExternalStartupData(snapshot_directory);
#if V8_ENABLE_WEBASSEMBLY && V8_TRAP_HANDLER_SUPPORTED
constexpr bool kUseDefaultTrapHandler = true;
CHECK(v8::V8::EnableWebAssemblyTrapHandler(kUseDefaultTrapHandler));
#endif // V8_ENABLE_WEBASSEMBLY && V8_TRAP_HANDLER_SUPPORTED
CcTest::set_array_buffer_allocator(
v8::ArrayBuffer::Allocator::NewDefaultAllocator());
v8::RegisterExtension(std::make_unique<i::PrintExtension>());
v8::RegisterExtension(std::make_unique<i::ProfilerExtension>());
v8::RegisterExtension(std::make_unique<i::TraceExtension>());
if (!initialize_) {
CHECK_NE(initialization_state_, kInitialized);
initialization_state_ = kUninitialized;
CHECK_NULL(isolate_);
} else {
CHECK_NE(initialization_state_, kUninitialized);
initialization_state_ = kInitialized;
CHECK_NULL(isolate_);
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator = allocator_;
isolate_ = v8::Isolate::New(create_params);
isolate_->Enter();
}
#ifdef DEBUG
const size_t active_isolates = i::Isolate::non_disposed_isolates();
#endif // DEBUG
callback_();
#ifdef DEBUG
// This DCHECK ensures that all Isolates are properly disposed after finishing
// the test. Stray Isolates lead to stray tasks in the platform which can
// interact weirdly when swapping in new platforms (for testing) or during
// shutdown.
DCHECK_EQ(active_isolates, i::Isolate::non_disposed_isolates());
#endif // DEBUG
if (initialize_) {
if (i_isolate()->was_locker_ever_used()) {
v8::Locker locker(isolate_);
EmptyMessageQueues(isolate_);
} else {
EmptyMessageQueues(isolate_);
}
isolate_->Exit();
isolate_->Dispose();
isolate_ = nullptr;
} else {
CHECK_NULL(isolate_);
}
v8::V8::Dispose();
cppgc::ShutdownProcess();
v8::V8::DisposePlatform();
}
i::Heap* CcTest::heap() { return i_isolate()->heap(); }
i::ReadOnlyHeap* CcTest::read_only_heap() {
return i_isolate()->read_only_heap();
}
void CcTest::AddGlobalFunction(v8::Local<v8::Context> env, const char* name,
v8::FunctionCallback callback) {
v8::Local<v8::FunctionTemplate> func_template =
v8::FunctionTemplate::New(isolate_, callback);
v8::Local<v8::Function> func =
func_template->GetFunction(env).ToLocalChecked();
func->SetName(v8_str(name));
env->Global()->Set(env, v8_str(name), func).FromJust();
}
void CcTest::CollectGarbage(i::AllocationSpace space, i::Isolate* isolate) {
i::Isolate* iso = isolate ? isolate : i_isolate();
iso->heap()->CollectGarbage(space, i::GarbageCollectionReason::kTesting);
}
void CcTest::CollectAllGarbage(i::Isolate* isolate) {
i::Isolate* iso = isolate ? isolate : i_isolate();
iso->heap()->CollectAllGarbage(i::Heap::kNoGCFlags,
i::GarbageCollectionReason::kTesting);
}
void CcTest::CollectAllAvailableGarbage(i::Isolate* isolate) {
i::Isolate* iso = isolate ? isolate : i_isolate();
iso->heap()->CollectAllAvailableGarbage(i::GarbageCollectionReason::kTesting);
}
void CcTest::PreciseCollectAllGarbage(i::Isolate* isolate) {
i::Isolate* iso = isolate ? isolate : i_isolate();
iso->heap()->PreciseCollectAllGarbage(i::Heap::kNoGCFlags,
i::GarbageCollectionReason::kTesting);
}
i::Handle<i::String> CcTest::MakeString(const char* str) {
i::Isolate* isolate = CcTest::i_isolate();
i::Factory* factory = isolate->factory();
return factory->InternalizeUtf8String(str);
}
i::Handle<i::String> CcTest::MakeName(const char* str, int suffix) {
v8::base::EmbeddedVector<char, 128> buffer;
v8::base::SNPrintF(buffer, "%s%d", str, suffix);
return CcTest::MakeString(buffer.begin());
}
v8::base::RandomNumberGenerator* CcTest::random_number_generator() {
return InitIsolateOnce()->random_number_generator();
}
v8::Local<v8::Object> CcTest::global() {
return isolate()->GetCurrentContext()->Global();
}
void CcTest::InitializeVM() {
CHECK(!v8::base::Relaxed_Load(&isolate_used_));
CHECK(!initialize_called_);
initialize_called_ = true;
v8::HandleScope handle_scope(CcTest::isolate());
v8::Context::New(CcTest::isolate())->Enter();
}
v8::Local<v8::Context> CcTest::NewContext(CcTestExtensionFlags extension_flags,
v8::Isolate* isolate) {
const char* extension_names[kMaxExtensions];
int extension_count = 0;
for (int i = 0; i < kMaxExtensions; ++i) {
if (!extension_flags.contains(static_cast<CcTestExtensionId>(i))) continue;
extension_names[extension_count] = kExtensionName[i];
++extension_count;
}
v8::ExtensionConfiguration config(extension_count, extension_names);
v8::Local<v8::Context> context = v8::Context::New(isolate, &config);
CHECK(!context.IsEmpty());
return context;
}
LocalContext::~LocalContext() {
v8::HandleScope scope(isolate_);
v8::Local<v8::Context>::New(isolate_, context_)->Exit();
context_.Reset();
}
void LocalContext::Initialize(v8::Isolate* isolate,
v8::ExtensionConfiguration* extensions,
v8::Local<v8::ObjectTemplate> global_template,
v8::Local<v8::Value> global_object) {
v8::HandleScope scope(isolate);
v8::Local<v8::Context> context =
v8::Context::New(isolate, extensions, global_template, global_object);
context_.Reset(isolate, context);
context->Enter();
// We can't do this later perhaps because of a fatal error.
isolate_ = isolate;
}
// This indirection is needed because HandleScopes cannot be heap-allocated, and
// we don't want any unnecessary #includes in cctest.h.
class V8_NODISCARD InitializedHandleScopeImpl {
public:
explicit InitializedHandleScopeImpl(i::Isolate* isolate)
: handle_scope_(isolate) {}
private:
i::HandleScope handle_scope_;
};
InitializedHandleScope::InitializedHandleScope(i::Isolate* isolate)
: main_isolate_(isolate ? isolate : CcTest::InitIsolateOnce()),
initialized_handle_scope_impl_(
new InitializedHandleScopeImpl(main_isolate_)) {}
InitializedHandleScope::~InitializedHandleScope() = default;
HandleAndZoneScope::HandleAndZoneScope(bool support_zone_compression)
: main_zone_(
new i::Zone(&allocator_, ZONE_NAME, support_zone_compression)) {}
HandleAndZoneScope::~HandleAndZoneScope() = default;
i::Handle<i::JSFunction> Optimize(
i::Handle<i::JSFunction> function, i::Zone* zone, i::Isolate* isolate,
uint32_t flags, std::unique_ptr<i::compiler::JSHeapBroker>* out_broker) {
i::Handle<i::SharedFunctionInfo> shared(function->shared(), isolate);
i::IsCompiledScope is_compiled_scope(shared->is_compiled_scope(isolate));
CHECK(is_compiled_scope.is_compiled() ||
i::Compiler::Compile(isolate, function, i::Compiler::CLEAR_EXCEPTION,
&is_compiled_scope));
CHECK_NOT_NULL(zone);
i::OptimizedCompilationInfo info(zone, isolate, shared, function,
i::CodeKind::TURBOFAN);
if (flags & ~i::OptimizedCompilationInfo::kInlining) UNIMPLEMENTED();
if (flags & i::OptimizedCompilationInfo::kInlining) {
info.set_inlining();
}
CHECK(info.shared_info()->HasBytecodeArray());
i::JSFunction::EnsureFeedbackVector(isolate, function, &is_compiled_scope);
i::Handle<i::CodeT> code = i::ToCodeT(
i::compiler::Pipeline::GenerateCodeForTesting(&info, isolate, out_broker)
.ToHandleChecked(),
isolate);
info.native_context().AddOptimizedCode(*code);
function->set_code(*code, v8::kReleaseStore);
return function;
}
static void PrintTestList() {
for (auto [name, test] : g_cctests.Get()) {
printf("%s\n", name.c_str());
}
}
int main(int argc, char* argv[]) {
#if V8_OS_WIN
UINT new_flags =
SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX;
UINT existing_flags = SetErrorMode(new_flags);
SetErrorMode(existing_flags | new_flags);
#if V8_CC_MSVC
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
_set_error_mode(_OUT_TO_STDERR);
#endif // V8_CC_MSVC
#endif // V8_OS_WIN
std::string usage = "Usage: " + std::string(argv[0]) + " [--list]" +
" [[V8_FLAGS] CCTEST]\n\n" + "Options:\n" +
" --list: list all cctests\n" +
" CCTEST: cctest identfier returned by --list\n" +
" V8_FLAGS: see V8 options below\n\n\n";
#ifdef V8_USE_PERFETTO
// Set up the in-process backend that the tracing controller will connect to.
perfetto::TracingInitArgs init_args;
init_args.backends = perfetto::BackendType::kInProcessBackend;
perfetto::Tracing::Initialize(init_args);
#endif // V8_USE_PERFETTO
using HelpOptions = v8::internal::FlagList::HelpOptions;
v8::internal::FlagList::SetFlagsFromCommandLine(
&argc, argv, true, HelpOptions(HelpOptions::kExit, usage.c_str()));
const char* test_arg = nullptr;
for (int i = 1; i < argc; ++i) {
const char* arg = argv[i];
if (strcmp(arg, "--list") == 0) {
PrintTestList();
return 0;
}
if (*arg == '-') {
// Ignore flags that weren't removed by SetFlagsFromCommandLine
continue;
}
if (test_arg != nullptr) {
fprintf(stderr,
"Running multiple tests in sequence is not allowed. Use "
"tools/run-tests.py instead.\n");
return 1;
}
test_arg = arg;
}
if (test_arg == nullptr) {
printf("Ran 0 tests.\n");
return 0;
}
auto it = g_cctests.Get().find(test_arg);
if (it == g_cctests.Get().end()) {
fprintf(stderr, "ERROR: Did not find test %s.\n", test_arg);
return 1;
}
CcTest* test = it->second;
test->Run(argv[0]);
return 0;
}
RegisterThreadedTest* RegisterThreadedTest::first_ = nullptr;
int RegisterThreadedTest::count_ = 0;
bool IsValidUnwrapObject(v8::Object* object) {
i::Address addr = *reinterpret_cast<i::Address*>(object);
auto instance_type = i::Internals::GetInstanceType(addr);
return (v8::base::IsInRange(instance_type,
i::Internals::kFirstJSApiObjectType,
i::Internals::kLastJSApiObjectType) ||
instance_type == i::Internals::kJSObjectType ||
instance_type == i::Internals::kJSSpecialApiObjectType);
}
ManualGCScope::ManualGCScope(i::Isolate* isolate)
: flag_concurrent_marking_(i::FLAG_concurrent_marking),
flag_concurrent_sweeping_(i::FLAG_concurrent_sweeping),
flag_stress_concurrent_allocation_(i::FLAG_stress_concurrent_allocation),
flag_stress_incremental_marking_(i::FLAG_stress_incremental_marking),
flag_parallel_marking_(i::FLAG_parallel_marking),
flag_detect_ineffective_gcs_near_heap_limit_(
i::FLAG_detect_ineffective_gcs_near_heap_limit) {
// Some tests run threaded (back-to-back) and thus the GC may already be
// running by the time a ManualGCScope is created. Finalizing existing marking
// prevents any undefined/unexpected behavior.
if (isolate && isolate->heap()->incremental_marking()->IsMarking()) {
CcTest::CollectGarbage(i::OLD_SPACE, isolate);
}
i::FLAG_concurrent_marking = false;
i::FLAG_concurrent_sweeping = false;
i::FLAG_stress_incremental_marking = false;
i::FLAG_stress_concurrent_allocation = false;
// Parallel marking has a dependency on concurrent marking.
i::FLAG_parallel_marking = false;
i::FLAG_detect_ineffective_gcs_near_heap_limit = false;
}
ManualGCScope::~ManualGCScope() {
i::FLAG_concurrent_marking = flag_concurrent_marking_;
i::FLAG_concurrent_sweeping = flag_concurrent_sweeping_;
i::FLAG_stress_concurrent_allocation = flag_stress_concurrent_allocation_;
i::FLAG_stress_incremental_marking = flag_stress_incremental_marking_;
i::FLAG_parallel_marking = flag_parallel_marking_;
i::FLAG_detect_ineffective_gcs_near_heap_limit =
flag_detect_ineffective_gcs_near_heap_limit_;
}
v8::PageAllocator* TestPlatform::GetPageAllocator() {
return CcTest::default_platform()->GetPageAllocator();
}
void TestPlatform::OnCriticalMemoryPressure() {
CcTest::default_platform()->OnCriticalMemoryPressure();
}
bool TestPlatform::OnCriticalMemoryPressure(size_t length) {
return CcTest::default_platform()->OnCriticalMemoryPressure(length);
}
int TestPlatform::NumberOfWorkerThreads() {
return CcTest::default_platform()->NumberOfWorkerThreads();
}
std::shared_ptr<v8::TaskRunner> TestPlatform::GetForegroundTaskRunner(
v8::Isolate* isolate) {
return CcTest::default_platform()->GetForegroundTaskRunner(isolate);
}
void TestPlatform::CallOnWorkerThread(std::unique_ptr<v8::Task> task) {
CcTest::default_platform()->CallOnWorkerThread(std::move(task));
}
void TestPlatform::CallDelayedOnWorkerThread(std::unique_ptr<v8::Task> task,
double delay_in_seconds) {
CcTest::default_platform()->CallDelayedOnWorkerThread(std::move(task),
delay_in_seconds);
}
std::unique_ptr<v8::JobHandle> TestPlatform::PostJob(
v8::TaskPriority priority, std::unique_ptr<v8::JobTask> job_task) {
return CcTest::default_platform()->PostJob(priority, std::move(job_task));
}
double TestPlatform::MonotonicallyIncreasingTime() {
return CcTest::default_platform()->MonotonicallyIncreasingTime();
}
double TestPlatform::CurrentClockTimeMillis() {
return CcTest::default_platform()->CurrentClockTimeMillis();
}
bool TestPlatform::IdleTasksEnabled(v8::Isolate* isolate) {
return CcTest::default_platform()->IdleTasksEnabled(isolate);
}
v8::TracingController* TestPlatform::GetTracingController() {
return CcTest::default_platform()->GetTracingController();
}
namespace {
class ShutdownTask final : public v8::Task {
public:
ShutdownTask(v8::base::Semaphore* destruction_barrier,
v8::base::Mutex* destruction_mutex,
v8::base::ConditionVariable* destruction_condition,
bool* can_destruct)
: destruction_barrier_(destruction_barrier),
destruction_mutex_(destruction_mutex),
destruction_condition_(destruction_condition),
can_destruct_(can_destruct)
{}
void Run() final {
destruction_barrier_->Signal();
{
v8::base::MutexGuard guard(destruction_mutex_);
while (!*can_destruct_) {
destruction_condition_->Wait(destruction_mutex_);
}
}
destruction_barrier_->Signal();
}
private:
v8::base::Semaphore* const destruction_barrier_;
v8::base::Mutex* const destruction_mutex_;
v8::base::ConditionVariable* const destruction_condition_;
bool* const can_destruct_;
};
} // namespace