diff options
Diffstat (limited to 'replay/sv_publishtest.cpp')
| -rw-r--r-- | replay/sv_publishtest.cpp | 406 |
1 files changed, 406 insertions, 0 deletions
diff --git a/replay/sv_publishtest.cpp b/replay/sv_publishtest.cpp new file mode 100644 index 0000000..c981803 --- /dev/null +++ b/replay/sv_publishtest.cpp @@ -0,0 +1,406 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +//=======================================================================================// + +#include "sv_publishtest.h" +#include "spew.h" +#include "replay/replayutils.h" +#include "replaysystem.h" +#include "sv_basejob.h" +#include "sv_fileservercleanup.h" +#include "tier1/convar.h" +#include "sv_filepublish.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//---------------------------------------------------------------------------------------- + +const char *g_pAcceptableFileserverProtocols[] = { "http", "https", NULL }; +const char *g_pAcceptableOffloadProtocols[] = { "ftp", NULL }; + +//---------------------------------------------------------------------------------------- + +class CPublishTester +{ +public: + CPublishTester(); + ~CPublishTester(); + + bool Go(); + +private: + bool Test_Emptyness( const char *pDescription, const char *pStr, bool bPrintResult ); + bool Test_Hostname( const char *pHostname, const char *pProtocolExample ); + bool Test_Protocol( const char *pDescription, const char *pProtocol, const char **pAcceptableProtocols ); + bool Test_Port( int nPort ); + bool Test_Path( const char *pDescription, const char *pPath, bool bForwardSlashesAllowed, bool bBackslashesAllowed ); + bool Test_LocalWebServerCVars(); + bool Test_IO( const char *pFilename ); + bool Test_FilePublish( const char *pFilename, bool bOffload ); + bool Test_PublishedFileDelete( const char *pFullFilename, const char *pFilename, bool bOffload ); + bool Test_WaitingForPlayersCVar(); + void PrintBaseUrlWarning(); + + char *m_pGarbageBuffer; + CBaseJob *m_pJob; + CBaseJob *m_pCleanerJob; +}; + +//---------------------------------------------------------------------------------------- + +#define GARBAGE_BUFFER_SIZE ( 1024 * 1000 ) + +CPublishTester::CPublishTester() +: m_pGarbageBuffer( NULL ), + m_pJob( NULL ), + m_pCleanerJob( NULL ) +{ + m_pGarbageBuffer = new char[ GARBAGE_BUFFER_SIZE ]; +} + +CPublishTester::~CPublishTester() +{ + delete [] m_pGarbageBuffer; + + if ( m_pJob ) + { + m_pJob->Release(); + } + + if ( m_pCleanerJob ) + { + m_pCleanerJob->Release(); + } +} + +bool CPublishTester::Test_Hostname( const char *pHostname, const char *pProtocolExample ) +{ + if ( !Test_Emptyness( "Hostname", pHostname, false ) ) + return false; + + if ( V_strstr( pHostname, "://" ) ) + { + g_pBlockSpewer->PrintEventResult( false ); + CFmtStr fmtError( "Should not contain a protocol (e.g: %s)!", pProtocolExample ); + g_pBlockSpewer->PrintEventError( fmtError.Access() ); + return false; + } + + // Test IP lookup + char szIP[16]; + if ( !g_pEngine->NET_GetHostnameAsIP( pHostname, szIP, sizeof( szIP ) ) ) + { + g_pBlockSpewer->PrintEventResult( false ); + g_pBlockSpewer->PrintEventError( "DNS lookup failed!" ); + return false; + } + + g_pBlockSpewer->PrintEventResult( true ); + g_pBlockSpewer->PrintEmptyLine(); + + return true; +} + +bool CPublishTester::Test_Emptyness( const char *pDescription, const char *pStr, bool bPrintResult ) +{ + g_pBlockSpewer->PrintValue( pDescription, pStr ); + g_pBlockSpewer->PrintEventStartMsg( "Validating" ); + + if ( V_strlen( pStr ) == 0 ) + { + g_pBlockSpewer->PrintEventResult( false ); + g_pBlockSpewer->PrintEventError( "Empty!" ); + return false; + } + + if ( bPrintResult ) + { + g_pBlockSpewer->PrintEventResult( true ); + g_pBlockSpewer->PrintEmptyLine(); + } + + return true; +} + +bool CPublishTester::Test_Port( int nPort ) +{ + g_pBlockSpewer->PrintValue( "Port", Replay_va( "%i", nPort ) ); + g_pBlockSpewer->PrintEventStartMsg( "Validating" ); + + if ( nPort < 0 || nPort > 65535 ) + { + g_pBlockSpewer->PrintEventResult( false ); + g_pBlockSpewer->PrintEventError( "Port must be between 0 and 65535." ); + return false; + } + + g_pBlockSpewer->PrintEventResult( true ); + g_pBlockSpewer->PrintEmptyLine(); + + return true; +} + +bool CPublishTester::Test_Path( const char *pDescription, const char *pPath, bool bForwardSlashesAllowed, bool bBackslashesAllowed ) +{ + g_pBlockSpewer->PrintValue( pDescription, pPath ); + g_pBlockSpewer->PrintEventStartMsg( "Validating" ); + if ( V_strlen( pPath ) == 0 ) + { + g_pBlockSpewer->PrintEventResult( false ); + g_pBlockSpewer->PrintEventError( "Empty path not allowed." ); + return false; + } + + if ( !bBackslashesAllowed && V_strstr( pPath, "\\" ) ) + { + g_pBlockSpewer->PrintEventResult( false ); + g_pBlockSpewer->PrintEventError( "Backslashes not allowed!" ); + return false; + } + + if ( !bForwardSlashesAllowed && V_strstr( pPath, "/" ) ) + { + g_pBlockSpewer->PrintEventResult( false ); + g_pBlockSpewer->PrintEventError( "Forward slashes not allowed!" ); + return false; + } + + if ( V_strstr( pPath, "//" ) || V_strstr( pPath, "\\\\" ) ) + { + g_pBlockSpewer->PrintEventResult( false ); + g_pBlockSpewer->PrintEventError( "Double slash detected!" ); + return false; + } + + if ( V_strstr( pPath, ".." ) ) + { + g_pBlockSpewer->PrintEventResult( false ); + g_pBlockSpewer->PrintEventError( "\"..\" not allowed!" ); + return false; + } + + g_pBlockSpewer->PrintEventResult( true ); + g_pBlockSpewer->PrintEmptyLine(); + + return true; +} + +bool CPublishTester::Test_Protocol( const char *pDescription, const char *pProtocol, const char **pAcceptableProtocols ) +{ + g_pBlockSpewer->PrintValue( pDescription, pProtocol ); + g_pBlockSpewer->PrintEventStartMsg( "Validating" ); + + // Test to see if the input protocol is acceptable + bool bProtocolOK = false; + int i = 0; + while ( pAcceptableProtocols[ i ] ) + { + if ( V_strcmp( pAcceptableProtocols[ i++ ], pProtocol ) == 0 ) + { + bProtocolOK = true; + break; + } + } + + // Protocol allowed? + if ( !bProtocolOK ) + { + g_pBlockSpewer->PrintEventResult( false ); + CFmtStr fmtError( "Must be one of the following (case-sensitive): " ); + i = 0; + while ( pAcceptableProtocols[ i ] ) + fmtError.AppendFormat( "\"%s\" ", pAcceptableProtocols[ i++ ] ); + g_pBlockSpewer->PrintEventError( fmtError.Access() ); + return false; + } + + g_pBlockSpewer->PrintEventResult( true ); + g_pBlockSpewer->PrintEmptyLine(); + + return true; +} + + +bool CPublishTester::Test_LocalWebServerCVars() +{ + // NOTE: We use the raw cvar here as opposed to CServerReplayContext::GetLocalFileServerPath(), + // which actually fixes slashes. If the cvar is using incorrect slashes here for the given OS, + // this test will fail with a specific error message. + extern ConVar replay_local_fileserver_path; + if ( !Test_Path( "Path", replay_local_fileserver_path.GetString(), IsPosix(), IsWindows() ) ) + return false; + + return true; +} + +bool CPublishTester::Test_IO( const char *pFilename ) +{ + g_pBlockSpewer->PrintTestHeader( "Testing File I/O" ); + + // Print out temp directory so the context for this section is clear + g_pBlockSpewer->PrintValue( "Temp path", SV_GetTmpDir() ); + g_pBlockSpewer->PrintEmptyLine(); + + // Open the file + FileHandle_t hTmpFile = g_pFullFileSystem->Open( pFilename, "wb+" ); + g_pBlockSpewer->PrintEventStartMsg( "Opening temp file" ); + if ( !hTmpFile ) + { + g_pBlockSpewer->PrintEventResult( false ); + return false; + } + g_pBlockSpewer->PrintEventResult( true ); + + // Write the file + g_pBlockSpewer->PrintEventStartMsg( "Allocating test buffer" ); // Lie. + if ( !m_pGarbageBuffer ) + { + g_pBlockSpewer->PrintEventResult( false ); + return false; + } + g_pBlockSpewer->PrintEventResult( true ); + + g_pBlockSpewer->PrintEventStartMsg( "Writing temp file" ); + if ( g_pFullFileSystem->Write( m_pGarbageBuffer, GARBAGE_BUFFER_SIZE, hTmpFile ) != GARBAGE_BUFFER_SIZE ) + { + g_pBlockSpewer->PrintEventResult( false ); + return false; + } + g_pBlockSpewer->PrintEventResult( true ); + + // Close the file + g_pFullFileSystem->Close( hTmpFile ); + + return true; +} + +bool CPublishTester::Test_FilePublish( const char *pFilename, bool bOffload ) +{ + g_pBlockSpewer->PrintTestHeader( "Testing file publisher" ); + g_pBlockSpewer->PrintValue( "Fileserver type", "Local Web server" ); + + if ( !Test_LocalWebServerCVars() ) + return false; + + m_pJob = SV_CreateLocalPublishJob( pFilename ); + + g_pBlockSpewer->PrintEmptyLine(); + + // Run publish test + if ( !m_pJob || !SV_RunJobToCompletion( m_pJob ) ) + { + g_pBlockSpewer->PrintEventError( m_pJob->GetErrorStr() ); + return false; + } + + return true; +} + +bool CPublishTester::Test_PublishedFileDelete( const char *pFullFilename, const char *pFilename, bool bOffload ) +{ + g_pBlockSpewer->PrintTestHeader( "Testing fileserver delete" ); + + if ( bOffload ) + { + // Delete the file from the tmp dir + g_pFullFileSystem->RemoveFile( pFullFilename ); + } + + m_pCleanerJob = SV_CreateDeleteFileJob(); + IFileserverCleanerJob *pCleanerJobImp = SV_CastJobToIFileserverCleanerJob( m_pCleanerJob ); + pCleanerJobImp->AddFileForDelete( pFilename ); + if ( !m_pCleanerJob || !SV_RunJobToCompletion( m_pCleanerJob ) ) + { + g_pBlockSpewer->PrintEventError( m_pCleanerJob->GetErrorStr() ); + return false; + } + + return true; +} + +bool CPublishTester::Test_WaitingForPlayersCVar() +{ + ConVarRef mp_waitingforplayers_cancel( "mp_waitingforplayers_cancel" ); + if ( mp_waitingforplayers_cancel.IsValid() && mp_waitingforplayers_cancel.GetBool() ) + { + g_pBlockSpewer->PrintEventError( "mp_waitingforplayers_cancel must be 0 in order for replay to work!" ); + return false; + } + + return true; +} + +void CPublishTester::PrintBaseUrlWarning() +{ + g_pBlockSpewer->PrintEmptyLine(); + g_pBlockSpewer->PrintMsg( "If clients can't access the following URL via a Web" ); + g_pBlockSpewer->PrintMsg( "browser, they will not be able to download Replays." ); + g_pBlockSpewer->PrintEmptyLine(); + g_pBlockSpewer->PrintValue( "URL", Replay_GetDownloadURL() ); +} + +bool CPublishTester::Go() +{ + const bool bOffload = false; + + // Force anyone outside of Go() to use the block spewer until we're done. + CSpewScope SpewScope( g_pBlockSpewer ); + + g_pBlockSpewer->PrintMsg( "TESTING REPLAY SYSTEM CONFIGURATION..." ); + + // Fileserver convars + g_pBlockSpewer->PrintTestHeader( "Testing Fileserver ConVars (replay_fileserver_*)" ); + + // Test replay_fileserver_protocol + extern ConVar replay_fileserver_protocol; + if ( !Test_Protocol( "Protocol", replay_fileserver_protocol.GetString(), g_pAcceptableFileserverProtocols ) ) + return false; + + extern ConVar replay_fileserver_host; + if ( !Test_Hostname( replay_fileserver_host.GetString(), "\"http\" or \"https\"" ) ) + return false; + + extern ConVar replay_fileserver_port; + if ( !Test_Port( replay_fileserver_port.GetInt() ) ) + return false; + + extern ConVar replay_fileserver_path; + if ( !Test_Path( "Path", replay_fileserver_path.GetString(), true, false ) ) + return false; + + // Print out the base URL / warning + PrintBaseUrlWarning(); + + CFmtStr fmtTmpFilename( "testpublish_%i.tmp", (int)abs( rand() % 10000 ) ); + CFmtStr fmtTmpFilenameFullPath( "%s%s", SV_GetTmpDir(), fmtTmpFilename.Access() ); + const char *pFilename = fmtTmpFilenameFullPath.Access(); + + // Test file I/O + if ( !Test_IO( pFilename ) ) + return false; + + // Get out if necessary + if ( !Test_FilePublish( pFilename, bOffload ) ) + return false; + + // Test delete from fileserver + if ( !Test_PublishedFileDelete( pFilename, fmtTmpFilename.Access(), bOffload ) ) + return false; + + // Make sure mp_waitingforplayers_cancel isn't on or replay will be fucked. + if ( !Test_WaitingForPlayersCVar() ) + return false; + + return true; +} + +//---------------------------------------------------------------------------------------- + +bool SV_DoTestPublish() +{ + CPublishTester tester; + return tester.Go(); +} + +//---------------------------------------------------------------------------------------- |