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
# Build and run tests in one command
cmake --build . --target unittests
The unittests target automatically runs all tests after building.
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
Basic Assertions
Table-Driven Tests
Type Tests
Fixture Tests
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
}
TEST (BitField, Read)
{
// Table of test values
static u64 table[] = {
0x 0000000000000000 ull ,
0x ffffffffffffffff ull ,
0x 7fffffffffffffff ull ,
0x 8000000000000000 ull ,
};
TestUnion object;
for (u64 val : table)
{
object . hex = val;
EXPECT_EQ (val, object . full_u64 );
EXPECT_EQ ( * (s64 * ) & val, object . full_s64 );
}
}
TEST (BitField, Storage)
{
TestUnion object;
// Verify memory layout
EXPECT_EQ (( void * ) & object . hex , ( void * ) & object . field );
// Verify sizes
EXPECT_EQ ( sizeof (TestUnion), sizeof ( object . hex ));
EXPECT_EQ ( sizeof (TestUnion), sizeof ( object . field ));
}
class MyTestFixture : public :: testing :: Test
{
protected:
void SetUp () override
{
// Initialize test data
test_value = 42 ;
}
void TearDown () override
{
// Clean up
}
int test_value;
};
TEST_F (MyTestFixture, TestWithFixture)
{
EXPECT_EQ ( 42 , test_value);
}
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 & 0x 7 , 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
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 );
}
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
)
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
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) { }
Use descriptive names
Test names should clearly describe what is being tested: TEST (BitField, ReadOperationsPreserveUnderlyingData) // Good
TEST (BitField, Test1) // Bad
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.
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 ));
}
Keep tests fast
Unit tests should run quickly. Avoid slow operations or use test fixtures to share expensive setup.
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