Skip to main content

Overview

Dolphin uses Google Test (gtest) as its unit testing framework. The test suite helps ensure code quality and prevent regressions across the emulator’s various components.

Test Organization

Tests are organized in the Source/UnitTests/ directory by component:
Source/UnitTests/
├── Common/          # Common utilities tests
├── Core/            # Core emulation tests
│   ├── PowerPC/     # PowerPC CPU tests
│   ├── IOS/         # IOS system tests
│   └── DSP/         # DSP tests
├── VideoCommon/     # Video backend tests
├── UnitTestsMain.cpp
└── StubHost.cpp

Running Tests

Build and Execute

# Build the test target
cmake --build . --target tests

# Run all tests
ctest --output-on-failure

# Or run the tests executable directly
./Binaries/Tests/tests

Running Specific Tests

# Run tests matching a pattern
./Binaries/Tests/tests --gtest_filter="BitField.*"

# Run a specific test case
./Binaries/Tests/tests --gtest_filter="BitField.Storage"

# List all available tests
./Binaries/Tests/tests --gtest_list_tests

Writing Unit Tests

Test Structure

Dolphin tests use the standard Google Test macros and structure:
// Copyright [year] Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <gtest/gtest.h>

#include "Common/BitField.h"
#include "Common/CommonTypes.h"

TEST(ComponentName, TestName)
{
  // Arrange
  int value = 42;
  
  // Act
  int result = ProcessValue(value);
  
  // Assert
  EXPECT_EQ(84, result);
}

Common Test Patterns

TEST(MathUtils, BasicOperations)
{
  EXPECT_EQ(4, 2 + 2);           // Equality
  EXPECT_NE(5, 2 + 2);           // Not equal
  EXPECT_LT(3, 5);               // Less than
  EXPECT_LE(3, 3);               // Less or equal
  EXPECT_GT(5, 3);               // Greater than
  EXPECT_GE(5, 5);               // Greater or equal
  EXPECT_TRUE(condition);         // Boolean true
  EXPECT_FALSE(!condition);       // Boolean false
}

Real-World Example

Here’s an actual test from Source/UnitTests/Common/BitFieldTest.cpp:
TEST(BitField, Assignment)
{
  TestUnion object;

  for (u64 val : table)
  {
    // Assignments with fixed values
    object.full_u64 = val;
    EXPECT_EQ(val, object.full_u64);

    object.full_s64 = (s64)val;
    EXPECT_EQ(val, object.full_u64);

    object.regular_field_unsigned = val;
    EXPECT_EQ(val & 0x7, object.regular_field_unsigned);

    object.at_dword_boundary = val;
    EXPECT_EQ(((s64)(val << 60)) >> 60, object.at_dword_boundary);

    // Assignment from other BitField
    object.at_dword_boundary = object.regular_field_signed;
    EXPECT_EQ(object.regular_field_signed, object.at_dword_boundary);
  }
}

Test Categories

Common Component Tests

Tests for utility classes and common functionality:
  • BitField: Bitfield manipulation (BitFieldTest.cpp)
  • StringUtil: String operations (StringUtilTest.cpp)
  • MathUtil: Mathematical utilities (MathUtilTest.cpp)
  • FileUtil: File system operations (FileUtilTest.cpp)
  • Crypto: Cryptographic functions (SHA1Test.cpp, EcTest.cpp)
  • Threading: Thread synchronization primitives (MutexTest.cpp, EventTest.cpp)

Core Emulation Tests

Tests for CPU, memory, and system emulation:
  • PowerPC: CPU instruction tests (DivUtilsTest.cpp)
  • JIT: JIT compiler tests (Fres.cpp, Frsqrte.cpp, FPRF.cpp)
  • IOS: IOS filesystem and USB emulation (FileSystemTest.cpp, SkylandersTest.cpp)
  • DSP: DSP assembly and acceleration (DSPAssemblyTest.cpp, DSPAcceleratorTest.cpp)
  • Memory: Memory management (PageFaultTest.cpp, MMIOTest.cpp)

Video Tests

Tests for graphics rendering components:
  • VertexLoader: Vertex data processing (VertexLoaderTest.cpp)

Adding New Tests

1

Create test file

Create a new .cpp file in the appropriate directory under Source/UnitTests/:
// Source/UnitTests/Common/MyNewTest.cpp
#include <gtest/gtest.h>
#include "Common/MyNewFeature.h"

TEST(MyNewFeature, BasicFunctionality)
{
  // Test code here
  EXPECT_TRUE(true);
}
2

Update CMakeLists.txt

Add your test file to the appropriate CMakeLists.txt:
# Source/UnitTests/Common/CMakeLists.txt
add_dolphin_test(CommonTests
  # ... existing files ...
  MyNewTest.cpp
)
3

Build and run

Build and verify your tests pass:
cmake --build . --target tests
./Binaries/tests --gtest_filter="MyNewFeature.*"

Test Framework Details

Main Test Harness

The test suite is initialized in UnitTestsMain.cpp:
int main(int argc, char** argv)
{
  fmt::print(stderr, "Running main() from UnitTestsMain.cpp\n");
  Common::RegisterMsgAlertHandler(TestMsgHandler);

  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
The custom message handler prevents tests from breaking on assertions by automatically returning “yes” to any questions.

CMake Integration

The test build system is configured in Source/UnitTests/CMakeLists.txt:
enable_testing()
add_custom_target(unittests)
add_custom_command(TARGET unittests POST_BUILD 
  COMMAND ${CMAKE_CTEST_COMMAND} "--output-on-failure")

add_executable(tests EXCLUDE_FROM_ALL UnitTestsMain.cpp StubHost.cpp)
target_link_libraries(tests PRIVATE fmt::fmt gtest::gtest core uicommon)
add_test(NAME tests COMMAND tests)

macro(add_dolphin_test target)
  add_library(${target} OBJECT ${ARGN})
  target_link_libraries(${target} PUBLIC fmt::fmt gtest::gtest PRIVATE core uicommon)
  target_link_libraries(tests PRIVATE ${target})
endmacro()

Best Practices

1

Test one thing

Each test should verify a single aspect of functionality:
// Good
TEST(Parser, HandlesEmptyInput) { }
TEST(Parser, HandlesValidInput) { }

// Bad - tests too many things
TEST(Parser, Everything) { }
2

Use descriptive names

Test names should clearly describe what is being tested:
TEST(BitField, ReadOperationsPreserveUnderlyingData)  // Good
TEST(BitField, Test1)                                 // Bad
3

Prefer EXPECT over ASSERT

Use EXPECT_* instead of ASSERT_* to continue testing after failures:
EXPECT_EQ(expected, actual);  // Continues on failure
ASSERT_EQ(expected, actual);  // Stops on failure
Only use ASSERT_* when continuing after a failure would cause crashes.
4

Test edge cases

Include boundary conditions and error cases:
TEST(MathUtils, ClampHandlesBoundaries)
{
  EXPECT_EQ(0, Clamp(-1, 0, 10));
  EXPECT_EQ(10, Clamp(11, 0, 10));
  EXPECT_EQ(5, Clamp(5, 0, 10));
}
5

Keep tests fast

Unit tests should run quickly. Avoid slow operations or use test fixtures to share expensive setup.
6

Make tests independent

Tests should not depend on each other or on execution order.
Always run the full test suite before submitting a pull request to ensure your changes don’t break existing functionality.

Continuous Integration

Dolphin’s CI system automatically runs all tests on every pull request. Tests must pass before code can be merged.

Next Steps

Code Style

Review C++ coding standards

Debugging

Learn debugging techniques