using System; using System.Linq; using NUnit.Framework; using Unity.Multiplayer.Center.Common; using Unity.Multiplayer.Center.Questionnaire; using Unity.Multiplayer.Center.Recommendations; namespace Unity.MultiplayerCenterTests { /// /// Basic test for the recommender system, without checking that the recommendation itself make sense. /// [TestFixture] class RecommenderSystemUnitTests { const string k_NoNetcodeTitle = "No Netcode"; [SetUp] [TearDown] public void Cleanup() { UnityEngine.Object.DestroyImmediate(RecommenderSystemDataObject.instance); // force reload from disk if accessed } [Test] public void TestEmptyQuestionnaireAndAnswer_ThrowsArgumentException() { var questionnaireData = new QuestionnaireData(); var answerData = new AnswerData(); // Having an empty questionnaire is an invalid state and should be caught. Assert.Throws(() => RecommenderSystem.GetRecommendation(questionnaireData, answerData)); } [Test] public void TestEmptyQuestionnaire_ThrowsArgumentException() { var questionnaireData = new QuestionnaireData(); var answerData = UtilsForRecommendationTests.BuildAnswerMatching(UtilsForRecommendationTests.GetProjectQuestionnaire()); // Having an empty questionnaire is an invalid state and should be caught. Assert.Throws(() => RecommenderSystem.GetRecommendation(questionnaireData, answerData)); } [Test] public void TestEmptyAnswer_ReturnsNull() { var questionnaireData = UtilsForRecommendationTests.GetProjectQuestionnaire(); var answerData = new AnswerData(); // Having empty answers is a normal state, where we don't have any recommendation to make. var recommendation = RecommenderSystem.GetRecommendation(questionnaireData, answerData); Assert.IsNull(recommendation); } [Test] public void TestGetRecommendationForMatchingAnswers_NothingNull() { var questionnaireData = UtilsForRecommendationTests.GetProjectQuestionnaire(); var answerData = UtilsForRecommendationTests.BuildAnswerMatching(questionnaireData); var recommendation = RecommenderSystem.GetRecommendation(questionnaireData, answerData); Assert.NotNull(recommendation); foreach (var solution in recommendation.NetcodeOptions) { if (solution.Solution is PossibleSolution.CustomNetcode or PossibleSolution.NoNetcode) continue; UtilsForRecommendationTests.AssertRecommendedSolutionNotNull(solution); } foreach (var solution in recommendation.ServerArchitectureOptions) { UtilsForRecommendationTests.AssertRecommendedSolutionNotNull(solution, false); } } [Test] public void TestSolutionToPackageViewData_NothingNull() { var allPackages = RecommenderSystem.GetSolutionsToRecommendedPackageViewData(); UtilsForRecommendationTests.AssertAllRecommendedPackageNotNull(allPackages); } [Test] public void TestGetRecommendationForMatchingAnswers_OnlyOneMainArchitecturePerCategory() { var questionnaireData = UtilsForRecommendationTests.GetProjectQuestionnaire(); var answerData = UtilsForRecommendationTests.BuildAnswerMatching(questionnaireData); var recommendation = RecommenderSystem.GetRecommendation(questionnaireData, answerData); AssertNoNetcodeIsTheLastNetcodeRecommendation(recommendation); Assert.AreEqual(1, recommendation.NetcodeOptions.Count(x => x.RecommendationType == RecommendationType.MainArchitectureChoice)); Assert.AreEqual(1, recommendation.NetcodeOptions.Count(x => x.Selected)); Assert.AreEqual(1, recommendation.ServerArchitectureOptions.Count(x => x.RecommendationType == RecommendationType.MainArchitectureChoice)); Assert.AreEqual(1, recommendation.ServerArchitectureOptions.Count(x => x.Selected)); } [Test] public void TestGetSolutionsToRecommendedPackageViewData_AllSelectionsHaveSameCount() { var allPackages = RecommenderSystem.GetSolutionsToRecommendedPackageViewData(); var expectedCount = 16; // 4 netcode options * 4 hosting options Assert.AreEqual(expectedCount, allPackages.Selections.Length); PossibleSolution[] netcodeSolutions = {PossibleSolution.NGO, PossibleSolution.N4E, PossibleSolution.CustomNetcode, PossibleSolution.NoNetcode}; PossibleSolution[] hostingSolutions = {PossibleSolution.LS, PossibleSolution.DS, PossibleSolution.CloudCode, PossibleSolution.DA}; var referencePackageCount = allPackages.GetPackagesForSelection(PossibleSolution.NGO, PossibleSolution.LS).Length; Assert.True(referencePackageCount > 0); foreach (var netcode in netcodeSolutions) { foreach (var hosting in hostingSolutions) { var packages = allPackages.GetPackagesForSelection(netcode, hosting); Assert.NotNull(packages); Assert.AreEqual(referencePackageCount, packages.Length); } } } [TestCase(PossibleSolution.DS)] [TestCase(PossibleSolution.LS)] [TestCase(PossibleSolution.DA)] [TestCase(PossibleSolution.CloudCode)] public void RecommendationData_AllHostingOverridesExistInNetcodeData(PossibleSolution hostingModel) { var data = RecommenderSystemDataObject.instance.RecommenderSystemData; var hostingOverrides = data.SolutionsByType[hostingModel].RecommendedPackages; var netcodePackages = data.SolutionsByType[PossibleSolution.NGO].RecommendedPackages; foreach (var package in hostingOverrides) { var index = Array.FindIndex(netcodePackages, p => p.PackageId == package.PackageId); Assert.True(index > -1, $"Did not find package {package.PackageId} in Netcode packages for {hostingModel}"); } } [TestCase(PossibleSolution.NGO)] [TestCase(PossibleSolution.N4E)] [TestCase(PossibleSolution.CustomNetcode)] [TestCase(PossibleSolution.NoNetcode)] public void RecommendationData_NetcodeSolutionsHaveRecommendationDataForAllPackages(PossibleSolution netcode) { var data = RecommenderSystemDataObject.instance.RecommenderSystemData; var packagesForNetcode = data.SolutionsByType[netcode].RecommendedPackages; var solutionPackages = data.RecommendedSolutions.Where(e => e.MainPackageId != null).Select(e => e.MainPackageId).ToArray(); foreach (var package in data.Packages) { if(solutionPackages.Contains(package.Id)) continue; var index = Array.FindIndex(packagesForNetcode, p => p.PackageId == package.Id); Assert.True(index > -1, $"Did not find package {package.Id} in packages of {netcode}"); } } // Regression test: selecting back and forth between netcode options should not change whether some solutions // are compatible or not [Test] public void TestAdaptRecommendationToNetcodeSelection_SelectBackAndForthDoesNotChangeRecommendations() { var recommendation = UtilsForRecommendationTests.ComputeRecommendationForPreset(Preset.Adventure); var selectedNetcode = RecommendationUtils.GetSelectedNetcode(recommendation); Assert.AreEqual(PossibleSolution.NGO, selectedNetcode.Solution, "Wrong test setup: expected NGO to be selected"); AssertCompatibilityInView(true, PossibleSolution.DA, recommendation.ServerArchitectureOptions, "Wrong test setup: expected DA to be compatible"); UtilsForRecommendationTests.SimulateSelectionChange(PossibleSolution.N4E, recommendation.NetcodeOptions); RecommenderSystem.AdaptRecommendationToNetcodeSelection(recommendation); Assert.AreEqual(PossibleSolution.N4E, RecommendationUtils.GetSelectedNetcode(recommendation).Solution, "Wrong test setup: expected N4E to be selected"); AssertCompatibilityInView(false, PossibleSolution.DA, recommendation.ServerArchitectureOptions,"Wrong test setup: expected DA to be incompatible with N4E"); UtilsForRecommendationTests.SimulateSelectionChange(PossibleSolution.NGO, recommendation.NetcodeOptions); RecommenderSystem.AdaptRecommendationToNetcodeSelection(recommendation); Assert.AreEqual(PossibleSolution.NGO, RecommendationUtils.GetSelectedNetcode(recommendation).Solution, "Wrong test setup: expected NGO to be selected"); AssertCompatibilityInView(true, PossibleSolution.DA, recommendation.ServerArchitectureOptions, "Fail: expected DA to still be compatible with NGO after selecting other solution"); } [Test] public void TestAdaptIncompatibility_AllValuesMatch() { bool ngoDaCompatibleBefore = AreCompatible(PossibleSolution.NGO, PossibleSolution.DA); Assert.True(ngoDaCompatibleBefore, "Wrong test setup: expected NGO to be compatible with DA"); var ngoSolBefore = GetSolution(PossibleSolution.NGO); Assert.AreEqual(-1, Array.FindIndex(ngoSolBefore.IncompatibleSolutions, s => s.Solution == PossibleSolution.DA), "Wrong test setup: expected DA to be in the incompatible list of NGO"); RecommenderSystemDataObject.instance.RecommenderSystemData.UpdateIncompatibility(PossibleSolution.NGO, PossibleSolution.DA, false, "test"); bool ngoDaCompatibleAfter = AreCompatible(PossibleSolution.NGO, PossibleSolution.DA); Assert.False(ngoDaCompatibleAfter); var ngoSolAfter = GetSolution(PossibleSolution.NGO); Assert.AreNotEqual(-1, Array.FindIndex(ngoSolAfter.IncompatibleSolutions, s => s.Solution == PossibleSolution.DA)); RecommenderSystemDataObject.instance.RecommenderSystemData.UpdateIncompatibility(PossibleSolution.NGO, PossibleSolution.DA, true); bool ngoDaCompatibleEnd = AreCompatible(PossibleSolution.NGO, PossibleSolution.DA); Assert.True(ngoDaCompatibleEnd, "Wrong test setup: expected NGO to be compatible with DA"); var ngoSolEnd = GetSolution(PossibleSolution.NGO); Assert.AreEqual(-1, Array.FindIndex(ngoSolEnd.IncompatibleSolutions, s => s.Solution == PossibleSolution.DA)); } static bool AreCompatible(PossibleSolution netcode, PossibleSolution hostingModel) => RecommenderSystemDataObject.instance.RecommenderSystemData.IsHostingModelCompatibleWithNetcode( PossibleSolution.NGO, PossibleSolution.DA, out _); static RecommendedSolution GetSolution(PossibleSolution type) => RecommenderSystemDataObject.instance.RecommenderSystemData.SolutionsByType[type]; static void AssertNoNetcodeIsTheLastNetcodeRecommendation(RecommendationViewData recommendation) { var netcodeSolutionCount = recommendation.NetcodeOptions.Length; Assert.AreEqual(4, netcodeSolutionCount); var lastSolution = recommendation.NetcodeOptions[netcodeSolutionCount - 1]; Assert.AreEqual(k_NoNetcodeTitle, lastSolution.Title); Assert.False(lastSolution.Selected); } static void AssertCompatibilityInView(bool expectedCompatible, PossibleSolution hostingModel, RecommendedSolutionViewData[] hostingSolutions, string msg) { foreach (var hosting in hostingSolutions) { if(hosting.Solution == hostingModel) { if(expectedCompatible) Assert.AreNotEqual(RecommendationType.Incompatible, hosting.RecommendationType, msg); else Assert.AreEqual(RecommendationType.Incompatible, hosting.RecommendationType, msg); } } } } }