summaryrefslogtreecommitdiff
path: root/replay/sv_publishtest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'replay/sv_publishtest.cpp')
-rw-r--r--replay/sv_publishtest.cpp406
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();
+}
+
+//----------------------------------------------------------------------------------------