// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.IO; using EpicGames.Core; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace EpicGames.Perforce.Fixture; /// /// Base test class for creating tests that use the Perforce fixture server. /// Every test invocation checks for the . /// If present, test continues and a PerforceConnection using that connection string is created. /// If not present, the test is marked as inconclusive, which is safe to ignore and doesn't fail the test suite. /// [TestClass] public abstract class BasePerforceFixtureTest : IDisposable { private const string EnvVar = "EPICGAMES_P4_FIXTURE_SERVER_URL"; private const string UseDefaultMarker = "default"; private const string DefaultUrl = "p4://test.user@localhost:1666"; /// /// Console-based logger factory /// protected ILoggerFactory LoggerFactory { get; } /// /// Temporary scratch dir, deleted after every test. /// Provides a location for (managed) workspace root or cache files. /// protected DirectoryReference TempDir { get; } /// /// A Perforce connection to the fixture test server /// protected PerforceConnection PerforceConnection { get; private set; } /// /// Fixture data mirroring how the stream/depot looks /// protected PerforceFixture Fixture { get; } = new(); private readonly string _clientName; protected BasePerforceFixtureTest() { LoggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder => builder.AddConsole()); // PerforceConnection is set in a [TestInitialize] method as Assert.Inconclusive cannot be raised in the constructor. // That initialize method will get called after the constructor ensuring the connection is instantiated. PerforceConnection = null!; _clientName = "test-fixture-" + Guid.NewGuid().ToString()[..8]; TempDir = new DirectoryReference(Path.Join(Path.GetTempPath(), "p4-" + _clientName)); DirectoryReference.CreateDirectory(TempDir); } [TestInitialize] public void SetUpPerforceConnection() { string? serverUrl = Environment.GetEnvironmentVariable(EnvVar); if (serverUrl == null) { // Only the first ~120 chars of the message is printed by MSTest runner. Try to keep it below that. Assert.Inconclusive($"P4 fixture server not configured. Set env var {EnvVar} to enable. See {nameof(BasePerforceFixtureTest)}."); } PerforceConnection = GetPerforceConnection(serverUrl!); } [TestCleanup] public void RemoveTempDir() { if (Directory.Exists(TempDir.FullName)) { // Remove the read-only flags set by the P4 client foreach (string filePath in Directory.EnumerateFiles(TempDir.FullName, "*", SearchOption.AllDirectories)) { FileInfo fileInfo = new FileInfo(filePath); fileInfo.IsReadOnly = false; } Directory.Delete(TempDir.FullName, true); } } private PerforceConnection GetPerforceConnection(string connectionString) { connectionString = connectionString == UseDefaultMarker ? DefaultUrl : connectionString; Uri uri = new(connectionString); Assert.AreEqual("p4", uri.Scheme, "P4 fixture server URL must start with p4://"); string serverAndPort = $"{uri.Host}:{uri.Port}"; (string username, string? password) = GetCredentialsFromUri(uri); PerforceSettings perforceSettings = new(serverAndPort, username) { AppName = "Perforce Test Fixture", ClientName = _clientName, PreferNativeClient = true }; if (password != null) { perforceSettings.Password = password; } return new PerforceConnection(perforceSettings, LoggerFactory.CreateLogger()); } private static (string username, string? password) GetCredentialsFromUri(Uri uri) { if (!uri.UserInfo.Contains(':', StringComparison.Ordinal)) { return (uri.UserInfo, null); } string[] parts = uri.UserInfo.Split(":"); return (parts[0], parts[1]); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Standard Dispose pattern method /// /// protected virtual void Dispose(bool disposing) { if (disposing) { LoggerFactory.Dispose(); } } }