@ -1,7 +1,12 @@
# include <gmock/gmock.h>
# include <gtest/gtest.h>
# include <cstddef>
# include <cstdint>
# include <iostream>
# include <string>
# include <string_view>
# include <variant>
# include <SDL.h>
# include <expected.hpp>
@ -17,13 +22,157 @@
# include "engine/surface.hpp"
# include "utils/paths.h"
# include "utils/sdl_wrap.h"
# include "utils/str_cat.hpp"
# include "utils/surface_to_png.hpp"
// Invoke with --update_expected to update the expected files with actual results.
static bool UpdateExpected ;
namespace devilution {
namespace {
constexpr char FixturesPath [ ] = " ../test/fixtures/text_render_integration_test/ " ;
struct TestFixture {
std : : string name ;
int width ;
int height ;
std : : string_view fmt ;
std : : vector < DrawStringFormatArg > args { } ;
TextRenderOptions opts { . flags = UiFlags : : ColorUiGold } ;
friend void PrintTo ( const TestFixture & f , std : : ostream * os )
{
* os < < f . name ;
}
} ;
const TestFixture Fixtures [ ] {
TestFixture {
. name = " basic " ,
. width = 96 ,
. height = 15 ,
. fmt = " DrawString " ,
} ,
TestFixture {
. name = " basic-colors " ,
. width = 186 ,
. height = 15 ,
. fmt = " {}{}{}{} " ,
. args = {
{ " Draw " , UiFlags : : ColorUiSilver } ,
{ " String " , UiFlags : : ColorUiGold } ,
{ " With " , UiFlags : : ColorUiSilverDark } ,
{ " Colors " , UiFlags : : ColorUiGoldDark } ,
} ,
} ,
TestFixture {
. name = " kerning_fit_spacing " ,
. width = 120 ,
. height = 15 ,
. fmt = " KerningFitSpacing " ,
. opts = {
. flags = UiFlags : : KerningFitSpacing | UiFlags : : ColorUiSilver ,
} ,
} ,
TestFixture {
. name = " kerning_fit_spacing-colors " ,
. width = 120 ,
. height = 15 ,
. fmt = " {}{}{} " ,
. args = {
{ " Kerning " , UiFlags : : ColorUiSilver } ,
{ " Fit " , UiFlags : : ColorUiGold } ,
{ " Spacing " , UiFlags : : ColorUiSilverDark } ,
} ,
. opts = {
. flags = UiFlags : : KerningFitSpacing ,
} ,
} ,
TestFixture {
. name = " kerning_fit_spacing__align_center " ,
. width = 170 ,
. height = 15 ,
. fmt = " KerningFitSpacing | AlignCenter " ,
. opts = {
. flags = UiFlags : : KerningFitSpacing | UiFlags : : AlignCenter | UiFlags : : ColorUiSilver ,
} ,
} ,
TestFixture {
. name = " kerning_fit_spacing__align_center-colors " ,
. width = 170 ,
. height = 15 ,
. fmt = " {}{}{} " ,
. args = {
{ " KerningFitSpacing " , UiFlags : : ColorUiSilver } ,
{ " | " , UiFlags : : ColorUiGold } ,
{ " AlignCenter " , UiFlags : : ColorUiSilverDark } ,
} ,
. opts = {
. flags = UiFlags : : KerningFitSpacing | UiFlags : : AlignCenter ,
} ,
} ,
TestFixture {
. name = " kerning_fit_spacing__align_center__newlines " ,
. width = 170 ,
. height = 42 ,
. fmt = " KerningFitSpacing | AlignCenter \n Short line \n Another overly long line " ,
. opts = {
. flags = UiFlags : : KerningFitSpacing | UiFlags : : AlignCenter | UiFlags : : ColorUiSilver ,
} ,
} ,
TestFixture {
. name = " kerning_fit_spacing__align_center__newlines_in_fmt-colors " ,
. width = 170 ,
. height = 42 ,
. fmt = " {} \n {} \n {} " ,
. args = {
{ " KerningFitSpacing | AlignCenter " , UiFlags : : ColorUiSilver } ,
{ " Short line " , UiFlags : : ColorUiGold } ,
{ " Another overly long line " , UiFlags : : ColorUiSilverDark } ,
} ,
. opts = {
. flags = UiFlags : : KerningFitSpacing | UiFlags : : AlignCenter ,
} ,
} ,
TestFixture {
. name = " kerning_fit_spacing__align_center__newlines_in_value-colors " ,
. width = 170 ,
. height = 42 ,
. fmt = " {}{} " ,
. args = {
{ " KerningFitSpacing | AlignCenter \n Short line \n Another overly " , UiFlags : : ColorUiSilver } ,
{ " long line " , UiFlags : : ColorUiGold } ,
} ,
. opts = {
. flags = UiFlags : : KerningFitSpacing | UiFlags : : AlignCenter ,
} ,
} ,
TestFixture {
. name = " kerning_fit_spacing__align_right " ,
. width = 170 ,
. height = 15 ,
. fmt = " KerningFitSpacing | AlignRight " ,
. opts = {
. flags = UiFlags : : KerningFitSpacing | UiFlags : : AlignRight | UiFlags : : ColorUiSilver ,
} ,
} ,
TestFixture {
. name = " kerning_fit_spacing__align_right-colors " ,
. width = 170 ,
. height = 15 ,
. fmt = " {}{}{} " ,
. args = {
{ " KerningFitSpacing " , UiFlags : : ColorUiSilver } ,
{ " | " , UiFlags : : ColorUiGold } ,
{ " AlignRight " , UiFlags : : ColorUiSilverDark } ,
} ,
. opts = {
. flags = UiFlags : : KerningFitSpacing | UiFlags : : AlignRight ,
} ,
} ,
} ;
SDLPaletteUniquePtr LoadPalette ( )
{
struct Color {
@ -31,7 +180,7 @@ SDLPaletteUniquePtr LoadPalette()
} ;
std : : array < Color , 256 > palData ;
LoadFileInMem ( " ui_art \\ diablo.pal " , palData ) ;
SDLPaletteUniquePtr palette = SDLWrap : : AllocPalette ( 256 ) ;
SDLPaletteUniquePtr palette = SDLWrap : : AllocPalette ( palData . size ( ) ) ;
for ( unsigned i = 0 ; i < palData . size ( ) ; i + + ) {
palette - > colors [ i ] = SDL_Color {
palData [ i ] . r , palData [ i ] . g , palData [ i ] . b , SDL_ALPHA_OPAQUE
@ -64,73 +213,85 @@ void DrawWithBorder(const Surface &out, const Rectangle &area, tl::function_ref<
Size { area . size . width - 2 , area . size . height - 2 } } ) ;
}
TEST ( TextRenderIntegrationTest , GoldenTest )
MATCHER_P ( FileContentsEq , expectedPath ,
StrCat ( negation ? " doesn't have " : " has " , " the same contents as " , : : testing : : PrintToString ( expectedPath ) ) )
{
if ( ReadFile ( arg ) ! = ReadFile ( expectedPath ) ) {
if ( UpdateExpected ) {
CopyFileOverwrite ( arg . c_str ( ) , expectedPath . c_str ( ) ) ;
std : : clog < < " ⬆️ Updated expected file at " < < expectedPath < < std : : endl ;
return true ;
}
return false ;
}
return true ;
}
class TextRenderIntegrationTest : public : : testing : : TestWithParam < TestFixture > {
public :
static void SetUpTestSuite ( )
{
palette = LoadPalette ( ) ;
}
static void TearDownTestSuite ( )
{
palette = nullptr ;
}
protected :
static SDLPaletteUniquePtr palette ;
} ;
SDLPaletteUniquePtr TextRenderIntegrationTest : : palette ;
TEST_P ( TextRenderIntegrationTest , RenderAndCompareTest )
{
SDLPaletteUniquePtr palette = LoadPalette ( ) ;
OwnedSurface out { Size { 200 , 140 } } ;
const TestFixture & fixture = GetParam ( ) ;
OwnedSurface out = OwnedSurface { fixture . width + 20 , fixture . height + 20 } ;
SDL_SetSurfacePalette ( out . surface , palette . get ( ) ) ;
ASSERT_NE ( out . surface , nullptr ) ;
int y = - 15 ;
DrawWithBorder ( out , Rectangle { Point { 0 , y + = 15 } , Size { 96 , 15 } } , [ & ] ( const Rectangle & rect ) {
DrawString ( out , " DrawString " , rect ,
TextRenderOptions { . flags = UiFlags : : ColorUiGold } ) ;
} ) ;
DrawWithBorder ( out , Rectangle { Point { 0 , y + = 15 } , Size { 120 , 15 } } , [ & ] ( const Rectangle & rect ) {
DrawString ( out , " KerningFitSpacing " , rect ,
TextRenderOptions { . flags = UiFlags : : KerningFitSpacing | UiFlags : : ColorUiSilver } ) ;
} ) ;
DrawWithBorder ( out , Rectangle { Point { 0 , y + = 15 } , Size { 170 , 15 } } , [ & ] ( const Rectangle & rect ) {
DrawString ( out , " KerningFitSpacing | AlignCenter " , rect ,
TextRenderOptions { . flags = UiFlags : : KerningFitSpacing | UiFlags : : AlignCenter | UiFlags : : ColorUiSilver } ) ;
} ) ;
DrawWithBorder ( out , Rectangle { Point { 0 , y + = 15 } , Size { 170 , 15 } } , [ & ] ( const Rectangle & rect ) {
DrawString ( out , " KerningFitSpacing | AlignRight " , rect ,
TextRenderOptions { . flags = UiFlags : : KerningFitSpacing | UiFlags : : AlignRight | UiFlags : : ColorUiSilver } ) ;
} ) ;
y + = 4 ;
DrawWithBorder ( out , Rectangle { Point { 0 , y + = 15 } , Size { 186 , 15 } } , [ & ] ( const Rectangle & rect ) {
DrawStringWithColors ( out , " {}{}{}{} " ,
{ { " Draw " , UiFlags : : ColorUiSilver } ,
{ " String " , UiFlags : : ColorUiGold } ,
{ " With " , UiFlags : : ColorUiSilverDark } ,
{ " Colors " , UiFlags : : ColorUiGoldDark } } ,
rect ) ;
} ) ;
DrawWithBorder ( out , Rectangle { Point { 0 , y + = 15 } , Size { 120 , 15 } } , [ & ] ( const Rectangle & rect ) {
DrawStringWithColors ( out , " {}{}{} " ,
{ { " Kerning " , UiFlags : : ColorUiSilver } ,
{ " Fit " , UiFlags : : ColorUiGold } ,
{ " Spacing " , UiFlags : : ColorUiSilverDark } } ,
rect ,
TextRenderOptions { . flags = UiFlags : : KerningFitSpacing } ) ;
} ) ;
DrawWithBorder ( out , Rectangle { Point { 0 , y + = 15 } , Size { 170 , 15 } } , [ & ] ( const Rectangle & rect ) {
DrawStringWithColors ( out , " {}{}{} " ,
{ { " KerningFitSpacing " , UiFlags : : ColorUiSilver } ,
{ " | " , UiFlags : : ColorUiGold } ,
{ " AlignCenter " , UiFlags : : ColorUiSilverDark } } ,
rect ,
TextRenderOptions { . flags = UiFlags : : KerningFitSpacing | UiFlags : : AlignCenter | UiFlags : : ColorUiSilver } ) ;
} ) ;
DrawWithBorder ( out , Rectangle { Point { 0 , y + = 15 } , Size { 170 , 15 } } , [ & ] ( const Rectangle & rect ) {
DrawStringWithColors ( out , " {}{}{} " ,
{ { " KerningFitSpacing " , UiFlags : : ColorUiSilver } ,
{ " | " , UiFlags : : ColorUiGold } ,
{ " AlignRight " , UiFlags : : ColorUiSilverDark } } ,
rect ,
TextRenderOptions { . flags = UiFlags : : KerningFitSpacing | UiFlags : : AlignRight | UiFlags : : ColorUiSilver } ) ;
DrawWithBorder ( out , Rectangle { Point { 10 , 10 } , Size { fixture . width , fixture . height } } , [ & ] ( const Rectangle & rect ) {
if ( fixture . args . empty ( ) ) {
DrawString ( out , fixture . fmt , rect , fixture . opts ) ;
} else {
DrawStringWithColors ( out , fixture . fmt , fixture . args , rect , fixture . opts ) ;
}
} ) ;
const std : : string actualPath = paths : : BasePath ( ) + FixturesPath + " actual.png " ;
const std : : string expectedPath = paths : : BasePath ( ) + FixturesPath + " expected .png" ;
const std : : string actualPath = StrCat ( paths : : BasePath ( ) , FixturesPath , GetParam ( ) . name , " -Actual.png " ) ;
const std : : string expectedPath = StrCat ( paths : : BasePath ( ) , FixturesPath , GetParam ( ) . name , " .png " ) ;
SDL_RWops * actual = SDL_RWFromFile ( actualPath . c_str ( ) , " wb " ) ;
ASSERT_NE ( actual , nullptr ) < < SDL_GetError ( ) ;
ASSERT_TRUE ( WriteSurfaceToFilePng ( out , actual ) . has_value ( ) ) ;
EXPECT_EQ ( ReadFile ( actualPath ) , ReadFile ( expectedPath ) ) < < " \n "
< < expectedPath < < " \n "
< < actualPath ;
EXPECT_THAT ( actualPath , FileContentsEq ( expectedPath ) ) ;
}
INSTANTIATE_TEST_SUITE_P ( GoldenTests , TextRenderIntegrationTest ,
testing : : ValuesIn ( Fixtures ) ,
[ ] ( const testing : : TestParamInfo < TestFixture > & info ) {
std : : string name = info . param . name ;
std : : replace ( name . begin ( ) , name . end ( ) , ' - ' , ' _ ' ) ;
return name ;
} ) ;
} // namespace
} // namespace devilution
int main ( int argc , char * * argv )
{
: : testing : : InitGoogleTest ( & argc , argv ) ;
if ( argc > = 2 ) {
for ( int i = 1 ; i < argc ; + + i ) {
if ( argv [ i ] ! = std : : string_view ( " --update_expected " ) ) {
std : : cerr < < " unknown argument: " < < argv [ i ] < < " \n Usage: "
< < argv [ 0 ] < < " [--update_expected] " < < " \n " ;
return 64 ;
}
}
UpdateExpected = true ;
}
return RUN_ALL_TESTS ( ) ;
}