test
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"createSeparatePackage": false
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96c71b811fa50403690b154e216fe217
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,211 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Unity.Multiplayer.Center.Analytics;
|
||||
using Unity.Multiplayer.Center.Common;
|
||||
using Unity.Multiplayer.Center.Recommendations;
|
||||
|
||||
namespace Unity.MultiplayerCenterTests
|
||||
{
|
||||
[TestFixture]
|
||||
internal class AnalyticsUtilsTests
|
||||
{
|
||||
[Test]
|
||||
public void AnalyticsUtils_GetQuestionDisplayNames_RightCount()
|
||||
{
|
||||
var questionnaire = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var questionDisplayNames = AnalyticsUtils.GetQuestionDisplayNames(questionnaire);
|
||||
Assert.AreEqual(questionDisplayNames.Count, questionnaire.Questions.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: this test checks exact values and has to be adapted in case those display names change.
|
||||
/// Why do that? To force the developers to think about the impact on the analytics!
|
||||
/// Contact the engine-data team and product.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AnalyticsUtils_GetQuestionDisplayNames_RightValuesForSelectedQuestions()
|
||||
{
|
||||
var questionnaire = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var questionDisplayNames = AnalyticsUtils.GetQuestionDisplayNames(questionnaire);
|
||||
Assert.AreEqual("Number of Players per Session", questionDisplayNames["PlayerCount"]);
|
||||
Assert.AreEqual("Gameplay Pace", questionDisplayNames["Pace"]);
|
||||
Assert.AreEqual("Cheating / Modding Prevention", questionDisplayNames["Cheating"]);
|
||||
Assert.AreEqual("Cost Sensitivity", questionDisplayNames["CostSensitivity"]);
|
||||
Assert.AreEqual("Netcode Architecture", questionDisplayNames["NetcodeArchitecture"]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyticsUtils_GetAnswerDisplayNames_RightCount()
|
||||
{
|
||||
var questionnaire = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var answerDisplayNames = AnalyticsUtils.GetAnswerDisplayNames(questionnaire);
|
||||
|
||||
// Note: 2 birds one stone: this test also nicely checks if we have duplicates with question ids
|
||||
var expectedCount = questionnaire.Questions.Sum(question => question.Choices.Length);
|
||||
Assert.AreEqual(answerDisplayNames.Count, expectedCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyticsUtils_GetPresetFullNames_RightCountAndIntValuesDidNotChange()
|
||||
{
|
||||
var presetFullNames = AnalyticsUtils.GetPresetFullNames();
|
||||
Assert.AreEqual(12, presetFullNames.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: this test checks exact values and has to be adapted in case those display names change.
|
||||
/// Why do that? To force the developers to think about the impact on the analytics!
|
||||
/// Contact the engine-data team and product.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AnalyticsUtils_GetPresetFullNames_RightValues()
|
||||
{
|
||||
var presetFullNames = AnalyticsUtils.GetPresetFullNames();
|
||||
Assert.AreEqual("-", presetFullNames[0]);
|
||||
Assert.AreEqual("Adventure", presetFullNames[1]);
|
||||
Assert.AreEqual("Shooter, Battle Royale, Battle Arena", presetFullNames[2]);
|
||||
Assert.AreEqual("Racing", presetFullNames[3]);
|
||||
Assert.AreEqual("Card Battle, Turn-based, Tabletop", presetFullNames[4]);
|
||||
Assert.AreEqual("Simulation", presetFullNames[5]);
|
||||
Assert.AreEqual("Strategy", presetFullNames[6]);
|
||||
Assert.AreEqual("Sports", presetFullNames[7]);
|
||||
Assert.AreEqual("Role-Playing, MMO", presetFullNames[8]);
|
||||
Assert.AreEqual("Async, Idle, Hyper Casual, Puzzle", presetFullNames[9]);
|
||||
Assert.AreEqual("Fighting", presetFullNames[10]);
|
||||
Assert.AreEqual("Arcade, Platformer, Sandbox", presetFullNames[11]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyticsUtils_ToGameSpecs_AllIdsAreInTheGameSpecs()
|
||||
{
|
||||
var questionnaire = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var answerDisplayNames = AnalyticsUtils.GetAnswerDisplayNames(questionnaire);
|
||||
var answerData = UtilsForRecommendationTests.BuildAnswerMatching(questionnaire);
|
||||
var questionIdToQuestionName = AnalyticsUtils.GetQuestionDisplayNames(questionnaire);
|
||||
var gameSpecs = AnalyticsUtils.ToGameSpecs(answerData, answerDisplayNames, questionIdToQuestionName);
|
||||
Assert.AreEqual(answerData.Answers.Count, gameSpecs.Length);
|
||||
|
||||
var questionIdsFromAnswerData = answerData.Answers.Select(answer => answer.QuestionId).ToArray();
|
||||
var questionIdsFromGameSpecs = gameSpecs.Select(spec => spec.QuestionId).ToArray();
|
||||
CollectionAssert.AreEquivalent(questionIdsFromAnswerData, questionIdsFromGameSpecs);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyticsUtils_ToGameSpecs_ValueCheck()
|
||||
{
|
||||
var answerDisplayNames = new Dictionary<string, string>(){
|
||||
{"A1", "Display Name A1"},
|
||||
{"A2", "Display Name A2"},
|
||||
{"A3", "Display Name A3"},
|
||||
{"A4", "Display Name A4"}
|
||||
};
|
||||
var questionIdToQuestionName = new Dictionary<string, string>()
|
||||
{
|
||||
{"Q1", "Display Name Q1"},
|
||||
{"Q2", "Display Name Q2"}
|
||||
};
|
||||
var answerData = new AnswerData()
|
||||
{
|
||||
Answers = new List<AnsweredQuestion>()
|
||||
{
|
||||
new() {QuestionId = "Q2", Answers = new List<string>() {"A3"}},
|
||||
new() {QuestionId = "Q1", Answers = new List<string>() {"A1"}}
|
||||
}
|
||||
};
|
||||
var gameSpecs = AnalyticsUtils.ToGameSpecs(answerData, answerDisplayNames, questionIdToQuestionName);
|
||||
|
||||
var expectedGameSpecs = new GameSpec[]
|
||||
{
|
||||
new (){ QuestionId = "Q2", QuestionText = "Display Name Q2", AcceptsMultipleAnswers = false, AnswerId = "A3", AnswerText = "Display Name A3"},
|
||||
new (){ QuestionId = "Q1", QuestionText = "Display Name Q1", AcceptsMultipleAnswers = false, AnswerId = "A1", AnswerText = "Display Name A1"},
|
||||
};
|
||||
CollectionAssert.AreEquivalent(expectedGameSpecs, gameSpecs);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyticsUtils_ToGameSpecs_NoEmptyString()
|
||||
{
|
||||
GetAnswersWithMatchingAnalyticsData(out var _, out var gameSpecs);
|
||||
foreach (var spec in gameSpecs)
|
||||
{
|
||||
Assert.False(string.IsNullOrEmpty(spec.QuestionId), spec.QuestionId);
|
||||
Assert.False(string.IsNullOrEmpty(spec.QuestionText), spec.QuestionText);
|
||||
Assert.False(string.IsNullOrEmpty(spec.AnswerId), spec.AnswerId);
|
||||
Assert.False(string.IsNullOrEmpty(spec.AnswerText), spec.AnswerText);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyticsUtils_AssumptionTest_HardCodedNumNetcodePackageMatchesRecommendations()
|
||||
{
|
||||
// note that the amount of possible netcode does not change with the answer data.
|
||||
var someRecommendation = UtilsForRecommendationTests.GetSomeRecommendation();
|
||||
var netcodePackageCount = someRecommendation.NetcodeOptions.Count(e
|
||||
=> !string.IsNullOrEmpty(e.MainPackage?.PackageId));
|
||||
|
||||
Assert.AreEqual(netcodePackageCount, AnalyticsUtils.NumNetcodePackage);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyticsUtils_AssumptionTest_HardCodedNumHostingPackageMatchesRecommendations()
|
||||
{
|
||||
// note that the amount of possible netcode does not change with the answer data.
|
||||
var someRecommendation = UtilsForRecommendationTests.GetSomeRecommendation();
|
||||
var hostingPackageCount = someRecommendation.ServerArchitectureOptions.Count(e
|
||||
=> !string.IsNullOrEmpty(e.MainPackage?.PackageId));
|
||||
|
||||
Assert.AreEqual(hostingPackageCount, AnalyticsUtils.NumHostingPackages);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyticsUtils_GetPackagesWithAnalyticsFormat_NetcodeValuesMakeSense()
|
||||
{
|
||||
const string ngo = "com.unity.netcode.gameobjects";
|
||||
const string n4e = "com.unity.netcode";
|
||||
const string tp = "com.unity.transport";
|
||||
|
||||
var someRecommendation = UtilsForRecommendationTests.GetSomeRecommendation();
|
||||
var packageViews = RecommenderSystem.GetSolutionsToRecommendedPackageViewData();
|
||||
var packagesAnalyticsFormat = AnalyticsUtils.GetPackagesWithAnalyticsFormat(someRecommendation, packageViews);
|
||||
|
||||
// all netcode packages are in the array
|
||||
Assert.AreEqual(1,packagesAnalyticsFormat.Count(e => e.PackageId == ngo) );
|
||||
Assert.AreEqual(1, packagesAnalyticsFormat.Count(e => e.PackageId == n4e));
|
||||
Assert.AreEqual(1, packagesAnalyticsFormat.Count(e => e.PackageId == tp));
|
||||
|
||||
var netcodePackages = packagesAnalyticsFormat.Where(e => e.PackageId is ngo or n4e or tp).ToArray();
|
||||
Assert.AreEqual(3, netcodePackages.Count()); // exactly 3 matches
|
||||
Assert.AreEqual(1, netcodePackages.Count(e => e.SelectedForInstall)); // 1 selected
|
||||
Assert.AreEqual(1, netcodePackages.Count(e => e.IsRecommended)); // 1 recommended
|
||||
|
||||
// Since we only look at the project direct dependencies, 1 netcode max can be installed in a normal setup
|
||||
// edge case would be: some installed ngo/n4e and manually added Transport to the manifest
|
||||
Assert.True(netcodePackages.Count(e => e.IsAlreadyInstalled) <= 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnalyticsUtils_GetPackagesWithAnalyticsFormat_AllPackagesAreIn()
|
||||
{
|
||||
var someRecommendation = UtilsForRecommendationTests.GetSomeRecommendation();
|
||||
var packageViews = RecommenderSystem.GetSolutionsToRecommendedPackageViewData();
|
||||
var packagesAnalyticsFormat = AnalyticsUtils.GetPackagesWithAnalyticsFormat(someRecommendation, packageViews);
|
||||
|
||||
var allPackages = RecommenderSystemDataObject.instance.RecommenderSystemData.PackageDetailsById.Keys;
|
||||
|
||||
foreach (var id in allPackages)
|
||||
{
|
||||
Assert.AreEqual(1, packagesAnalyticsFormat.Count(e => e.PackageId == id), id);
|
||||
}
|
||||
}
|
||||
|
||||
static void GetAnswersWithMatchingAnalyticsData(out AnswerData answerData, out GameSpec[] gameSpecs)
|
||||
{
|
||||
var questionnaire = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var answerDisplayNames = AnalyticsUtils.GetAnswerDisplayNames(questionnaire);
|
||||
answerData = UtilsForRecommendationTests.BuildAnswerMatching(questionnaire);
|
||||
var questionIdToQuestionName = AnalyticsUtils.GetQuestionDisplayNames(questionnaire);
|
||||
gameSpecs = AnalyticsUtils.ToGameSpecs(answerData, answerDisplayNames, questionIdToQuestionName);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2c1cf24bea5410ea530b2c8a875b293
|
||||
timeCreated: 1714641306
|
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly:InternalsVisibleTo("Unity.Multiplayer.Center.GettingStartedTab.Tests")]
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89ffa359141b49fea9bfa6a1db3b18be
|
||||
timeCreated: 1715084531
|
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using Unity.Multiplayer.Center.Common;
|
||||
using Unity.Multiplayer.Center.Questionnaire;
|
||||
|
||||
namespace Unity.MultiplayerCenterTests
|
||||
{
|
||||
partial class RecommendationTests
|
||||
{
|
||||
public static IEnumerable<TestCaseData> AdventurePresetCases
|
||||
=> new TestCaseData[]
|
||||
{
|
||||
new("2", PossibleSolution.NGO, PossibleSolution.DS, Preset.Adventure),
|
||||
new("4", PossibleSolution.NGO, PossibleSolution.DS, Preset.Adventure),
|
||||
new("8", PossibleSolution.NGO, PossibleSolution.DS, Preset.Adventure),
|
||||
new("16", PossibleSolution.NGO, PossibleSolution.DS, Preset.Adventure),
|
||||
new("64+", PossibleSolution.NGO, PossibleSolution.DS, Preset.Adventure),
|
||||
new("128", PossibleSolution.N4E, PossibleSolution.DS, Preset.Adventure)
|
||||
};
|
||||
|
||||
public static IEnumerable<TestCaseData> SandboxPresetCases
|
||||
=> new TestCaseData[]
|
||||
{
|
||||
new("2", PossibleSolution.NGO, PossibleSolution.DA, Preset.Sandbox),
|
||||
new("4", PossibleSolution.NGO, PossibleSolution.DA, Preset.Sandbox),
|
||||
new("8", PossibleSolution.NGO, PossibleSolution.DA, Preset.Sandbox),
|
||||
new("16", PossibleSolution.NGO, PossibleSolution.DA, Preset.Sandbox),
|
||||
new("64+", PossibleSolution.NGO, PossibleSolution.DA, Preset.Sandbox),
|
||||
new("128", PossibleSolution.N4E, PossibleSolution.DS, Preset.Sandbox)
|
||||
};
|
||||
|
||||
public static IEnumerable<TestCaseData> AsyncPresetCases
|
||||
=> new TestCaseData[]
|
||||
{
|
||||
new("2", PossibleSolution.NoNetcode, PossibleSolution.CloudCode, Preset.Async),
|
||||
new("4", PossibleSolution.NoNetcode, PossibleSolution.CloudCode, Preset.Async),
|
||||
new("8", PossibleSolution.NoNetcode, PossibleSolution.CloudCode, Preset.Async),
|
||||
new("16", PossibleSolution.NoNetcode, PossibleSolution.CloudCode, Preset.Async),
|
||||
new("64+", PossibleSolution.NoNetcode, PossibleSolution.CloudCode, Preset.Async),
|
||||
new("128", PossibleSolution.NoNetcode, PossibleSolution.CloudCode, Preset.Async)
|
||||
};
|
||||
|
||||
public static IEnumerable<TestCaseData> TurnBasedPresetCases
|
||||
=> new TestCaseData[]
|
||||
{
|
||||
new("2", PossibleSolution.NoNetcode, PossibleSolution.CloudCode, Preset.TurnBased),
|
||||
new("4", PossibleSolution.NoNetcode, PossibleSolution.CloudCode, Preset.TurnBased),
|
||||
new("8", PossibleSolution.NoNetcode, PossibleSolution.CloudCode, Preset.TurnBased),
|
||||
new("16", PossibleSolution.NoNetcode, PossibleSolution.CloudCode, Preset.TurnBased),
|
||||
new("64+", PossibleSolution.NoNetcode, PossibleSolution.CloudCode, Preset.TurnBased),
|
||||
new("128", PossibleSolution.NoNetcode, PossibleSolution.CloudCode, Preset.TurnBased)
|
||||
};
|
||||
|
||||
public static IEnumerable<TestCaseData> FightingPresetCases
|
||||
=> new TestCaseData[]
|
||||
{
|
||||
new("2", PossibleSolution.CustomNetcode, PossibleSolution.LS, Preset.Fighting),
|
||||
new("4", PossibleSolution.CustomNetcode, PossibleSolution.LS, Preset.Fighting),
|
||||
new("8", PossibleSolution.CustomNetcode, PossibleSolution.LS, Preset.Fighting),
|
||||
new("16", PossibleSolution.CustomNetcode, PossibleSolution.LS, Preset.Fighting),
|
||||
new("64+", PossibleSolution.CustomNetcode, PossibleSolution.LS, Preset.Fighting),
|
||||
new("128", PossibleSolution.CustomNetcode, PossibleSolution.LS, Preset.Fighting)
|
||||
};
|
||||
|
||||
public static IEnumerable<TestCaseData> RacingPresetCases
|
||||
=> new TestCaseData[]
|
||||
{
|
||||
new("2", PossibleSolution.N4E, PossibleSolution.DS, Preset.Racing),
|
||||
new("4", PossibleSolution.N4E, PossibleSolution.DS, Preset.Racing),
|
||||
new("8", PossibleSolution.N4E, PossibleSolution.DS, Preset.Racing),
|
||||
new("16", PossibleSolution.N4E, PossibleSolution.DS, Preset.Racing),
|
||||
new("64+", PossibleSolution.N4E, PossibleSolution.DS, Preset.Racing),
|
||||
new("128", PossibleSolution.N4E, PossibleSolution.DS, Preset.Racing)
|
||||
};
|
||||
|
||||
public static IEnumerable<TestCaseData> RolePlayingPresetCases
|
||||
=> new TestCaseData[]
|
||||
{
|
||||
new("2", PossibleSolution.CustomNetcode, PossibleSolution.DS, Preset.RolePlaying),
|
||||
new("4", PossibleSolution.CustomNetcode, PossibleSolution.DS, Preset.RolePlaying),
|
||||
new("8", PossibleSolution.CustomNetcode, PossibleSolution.DS, Preset.RolePlaying),
|
||||
new("16", PossibleSolution.CustomNetcode, PossibleSolution.DS, Preset.RolePlaying),
|
||||
new("64+", PossibleSolution.CustomNetcode, PossibleSolution.DS, Preset.RolePlaying),
|
||||
new("128", PossibleSolution.CustomNetcode, PossibleSolution.DS, Preset.RolePlaying)
|
||||
};
|
||||
|
||||
public static IEnumerable<TestCaseData> ShooterPresetCases
|
||||
=> new TestCaseData[]
|
||||
{
|
||||
new("2", PossibleSolution.N4E, PossibleSolution.DS, Preset.Shooter),
|
||||
new("4", PossibleSolution.N4E, PossibleSolution.DS, Preset.Shooter),
|
||||
new("8", PossibleSolution.N4E, PossibleSolution.DS, Preset.Shooter),
|
||||
new("16", PossibleSolution.N4E, PossibleSolution.DS, Preset.Shooter),
|
||||
new("64+", PossibleSolution.N4E, PossibleSolution.DS, Preset.Shooter),
|
||||
new("128", PossibleSolution.N4E, PossibleSolution.DS, Preset.Shooter)
|
||||
};
|
||||
|
||||
public static IEnumerable<TestCaseData> SimulationPresetCases
|
||||
=> new TestCaseData[]
|
||||
{
|
||||
new("2", PossibleSolution.NGO, PossibleSolution.DA, Preset.Simulation),
|
||||
new("4", PossibleSolution.NGO, PossibleSolution.DA, Preset.Simulation),
|
||||
new("8", PossibleSolution.NGO, PossibleSolution.DA, Preset.Simulation),
|
||||
new("16", PossibleSolution.NGO, PossibleSolution.DA, Preset.Simulation),
|
||||
new("64+", PossibleSolution.NGO, PossibleSolution.DA, Preset.Simulation),
|
||||
new("128", PossibleSolution.N4E, PossibleSolution.DS, Preset.Simulation)
|
||||
};
|
||||
|
||||
public static IEnumerable<TestCaseData> StrategyPresetCases
|
||||
=> new TestCaseData[]
|
||||
{
|
||||
new("2", PossibleSolution.CustomNetcode, PossibleSolution.LS, Preset.Strategy),
|
||||
new("4", PossibleSolution.CustomNetcode, PossibleSolution.LS, Preset.Strategy),
|
||||
new("8", PossibleSolution.CustomNetcode, PossibleSolution.LS, Preset.Strategy),
|
||||
new("16", PossibleSolution.CustomNetcode, PossibleSolution.LS, Preset.Strategy),
|
||||
new("64+", PossibleSolution.CustomNetcode, PossibleSolution.LS, Preset.Strategy),
|
||||
new("128", PossibleSolution.CustomNetcode, PossibleSolution.LS, Preset.Strategy)
|
||||
};
|
||||
|
||||
public static IEnumerable<TestCaseData> SportPresetCases
|
||||
=> new TestCaseData[]
|
||||
{
|
||||
new("2", PossibleSolution.N4E, PossibleSolution.DS, Preset.Sports),
|
||||
new("4", PossibleSolution.N4E, PossibleSolution.DS, Preset.Sports),
|
||||
new("8", PossibleSolution.N4E, PossibleSolution.DS, Preset.Sports),
|
||||
new("16", PossibleSolution.N4E, PossibleSolution.DS, Preset.Sports),
|
||||
new("64+", PossibleSolution.N4E, PossibleSolution.DS, Preset.Sports),
|
||||
new("128", PossibleSolution.N4E, PossibleSolution.DS, Preset.Sports)
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8110dd6ddbc04e58b50978562ce0c00a
|
||||
timeCreated: 1718365841
|
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Unity.Multiplayer.Center.Common;
|
||||
using Unity.Multiplayer.Center.Questionnaire;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.MultiplayerCenterTests
|
||||
{
|
||||
[TestFixture]
|
||||
class LogicTests
|
||||
{
|
||||
[Test]
|
||||
public void TryGetQuestionByQuestionId_IdExists_Found()
|
||||
{
|
||||
var questionnaire = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var success = Logic.TryGetQuestionByQuestionId(questionnaire, questionnaire.Questions[1].Id, out var question);
|
||||
Assert.True(success);
|
||||
Assert.NotNull(question);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryGetQuestionByQuestionId_StringIdExists_Found()
|
||||
{
|
||||
var questionnaire = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var success = Logic.TryGetQuestionByQuestionId(questionnaire, "Pace", out var question);
|
||||
Assert.True(success);
|
||||
Assert.NotNull(question);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryGetQuestionByQuestionId_IdDoesNotExist_NotFound()
|
||||
{
|
||||
var questionnaire = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var success = Logic.TryGetQuestionByQuestionId(questionnaire, "nonexistent", out var question);
|
||||
Assert.False(success);
|
||||
Assert.Null(question);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryGetAnswerByQuestionId_IdExists_Found()
|
||||
{
|
||||
var questionnaire = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var userAnswers = UtilsForRecommendationTests.BuildAnswerMatching(questionnaire);
|
||||
var success = Logic.TryGetAnswerByQuestionId(userAnswers, "PlayerCount", out var answer);
|
||||
Assert.True(success);
|
||||
Assert.NotNull(answer);
|
||||
Assert.AreEqual(1, answer.Answers.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryGetAnswerByQuestionId_IdDoesNotExist_NotFound()
|
||||
{
|
||||
var questionnaire = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var userAnswers = UtilsForRecommendationTests.BuildAnswerMatching(questionnaire);
|
||||
var success = Logic.TryGetAnswerByQuestionId(userAnswers, "nonexistent", out var answer);
|
||||
Assert.False(success);
|
||||
Assert.Null(answer);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestApplyPresetToAnswerData_WhenPlayerCountIsSet_PlayerCountStays()
|
||||
{
|
||||
var questionnaire = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var userAnswers = UtilsForRecommendationTests.BuildAnswerMatching(questionnaire);
|
||||
|
||||
Logic.TryGetAnswerByQuestionId(userAnswers, "PlayerCount", out var answer);
|
||||
var chosenPlayerCount = Int32.Parse(answer.Answers[0]);
|
||||
|
||||
// No default answer, otherwise this test could succeed even if the logic is wrong
|
||||
Assert.AreNotEqual(0, chosenPlayerCount);
|
||||
Assert.AreNotEqual(2, chosenPlayerCount);
|
||||
|
||||
var (newAnswer, newReco) = Logic.ApplyPresetToAnswerData(userAnswers, Preset.Strategy, questionnaire);
|
||||
Assert.NotNull(newAnswer);
|
||||
Logic.TryGetAnswerByQuestionId(newAnswer, "PlayerCount", out var newAnsweredQuestion);
|
||||
var newChosenPlayerCount = Int32.Parse(newAnsweredQuestion.Answers[0]);
|
||||
Assert.AreEqual(chosenPlayerCount, newChosenPlayerCount);
|
||||
Assert.NotNull(newReco);
|
||||
}
|
||||
|
||||
[TestCase("1.2", "1.3", true)]
|
||||
[TestCase("3.2", "1.3", false)]
|
||||
[TestCase("1.21", "1.2", false)]
|
||||
[TestCase("1.2", "1.2", false)]
|
||||
[TestCase("0.2", "1.3", true)]
|
||||
[TestCase("1.3.12", "1.3.13", true)]
|
||||
[TestCase("1.3.11", "0.23", false)]
|
||||
[TestCase("3.3.33", "3.3.33", false)]
|
||||
[TestCase("3.3.3", "3.3.33", true)]
|
||||
public void TestIsVersionLower_ReturnsCorrectResult(string versionToTest, string currentVersion, params bool[] expected)
|
||||
{
|
||||
var result = Logic.IsVersionLower(versionToTest, currentVersion);
|
||||
Assert.AreEqual(expected[0], result);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea491f91a8b84866ad13371715bf90c8
|
||||
timeCreated: 1710847764
|
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Unity.Multiplayer.Center.Window;
|
||||
using UnityEditor;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Unity.MultiplayerCenterTests
|
||||
{
|
||||
class QuickstartTabTests
|
||||
{
|
||||
bool m_IsQuickstartPackageInstalled = false;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetup()
|
||||
{
|
||||
var packInfo = UnityEditor.PackageManager.PackageInfo.FindForPackageName("com.unity.multiplayer.center.quickstart");
|
||||
m_IsQuickstartPackageInstalled = packInfo != null;
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
EditorWindow.GetWindow<MultiplayerCenterWindow>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void QuickstartTab_QuickstartPackageMissingHelpboxExists()
|
||||
{
|
||||
if (m_IsQuickstartPackageInstalled)
|
||||
{
|
||||
Assert.Ignore("Skip test because it needs the quickstart package to be missing");
|
||||
}
|
||||
|
||||
UtilsForGettingStartedTabTests.OpenGettingStartedTab();
|
||||
var helpBox = EditorWindow.GetWindow<MultiplayerCenterWindow>().rootVisualElement.Q<HelpBox>();
|
||||
Assert.NotNull(helpBox, "Helpbox for missing quickstart package not found");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29cbd1c6a8c9419aac357b8d4edbdc84
|
||||
timeCreated: 1721984533
|
@@ -0,0 +1,125 @@
|
||||
using NUnit.Framework;
|
||||
using Unity.Multiplayer.Center.Analytics;
|
||||
using Unity.Multiplayer.Center.Common;
|
||||
using Unity.Multiplayer.Center.Common.Analytics;
|
||||
using Unity.Multiplayer.Center.Window;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Unity.MultiplayerCenterTests
|
||||
{
|
||||
[TestFixture]
|
||||
internal class RecommendationAnalyticsTests
|
||||
{
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetup()
|
||||
{
|
||||
// Copy user choices to temp file to restore after tests.
|
||||
UtilsForMultiplayerCenterTests.CopyUserChoicesToTempFile();
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void OneTimeTearDown()
|
||||
{
|
||||
// restore user choices after tests.
|
||||
UtilsForMultiplayerCenterTests.RestoreUserChoicesFromTempFile();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
UtilsForMultiplayerCenterTests.CloseMultiplayerCenterWindow();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
UtilsForMultiplayerCenterTests.CloseMultiplayerCenterWindow();
|
||||
}
|
||||
|
||||
class MockThatCountsEvents : IMultiplayerCenterAnalytics
|
||||
{
|
||||
public int InstallationEventCount { get; private set; }
|
||||
public int RecommendationEventCount { get; private set; }
|
||||
|
||||
public void SendInstallationEvent(AnswerData data, Preset preset, Package[] packages)
|
||||
=> InstallationEventCount++;
|
||||
|
||||
public void SendRecommendationEvent(AnswerData data, Preset preset)
|
||||
=> RecommendationEventCount++;
|
||||
|
||||
public void SendGettingStartedInteractionEvent(string targetPackageId, string sectionId,
|
||||
InteractionDataType type, string displayName) { }
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RecommendationTabView_PresetSelectedViaUI_RecommendationEventSent()
|
||||
{
|
||||
UtilsForMultiplayerCenterTests.PopulateUserAnswersForPresetAndPlayerCount(Preset.Async, 4);
|
||||
|
||||
var view = CreateViewWithMockedAnalytics(out var mock);
|
||||
|
||||
Assert.NotNull(view.QuestionnaireView);
|
||||
|
||||
Assert.AreEqual(0, mock.RecommendationEventCount);
|
||||
|
||||
view.QuestionnaireView.RaisePresetSelected(Preset.Shooter);
|
||||
Assert.AreEqual(1, mock.RecommendationEventCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RecommendationTabView_NonePresetSelected_RecommendationEventNotSent()
|
||||
{
|
||||
UtilsForMultiplayerCenterTests.PopulateUserAnswersForPresetAndPlayerCount(Preset.Async, 4);
|
||||
|
||||
var view = CreateViewWithMockedAnalytics(out var mock);
|
||||
|
||||
Assert.NotNull(view.QuestionnaireView);
|
||||
|
||||
Assert.AreEqual(0, mock.RecommendationEventCount);
|
||||
|
||||
view.QuestionnaireView.RaisePresetSelected(Preset.None);
|
||||
Assert.AreEqual(0, mock.RecommendationEventCount); // no recommendation => no event
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RecommendationTabView_PlayerCountChangedViaUI_RecommendationEventSent()
|
||||
{
|
||||
UtilsForMultiplayerCenterTests.PopulateUserAnswersForPresetAndPlayerCount(Preset.Adventure, 8);
|
||||
|
||||
var view = CreateViewWithMockedAnalytics(out var mock);
|
||||
|
||||
Assert.NotNull(view.QuestionnaireView);
|
||||
|
||||
Assert.AreEqual(0, mock.RecommendationEventCount);
|
||||
|
||||
view.QuestionnaireView.QuestionUpdated(UtilsForMultiplayerCenterTests.CreatePlayerCountAnswer(2));
|
||||
Assert.AreEqual(1, mock.RecommendationEventCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RecommendationTabView_NonMandatoryAnswerChangedViaUI_RecommendationEventSent()
|
||||
{
|
||||
UtilsForMultiplayerCenterTests.PopulateUserAnswersForPresetAndPlayerCount(Preset.Adventure, 8);
|
||||
|
||||
var view = CreateViewWithMockedAnalytics(out var mock);
|
||||
|
||||
Assert.NotNull(view.QuestionnaireView);
|
||||
|
||||
Assert.AreEqual(0, mock.RecommendationEventCount);
|
||||
|
||||
view.QuestionnaireView.QuestionUpdated(UtilsForMultiplayerCenterTests.GetAnsweredQuestionThatIsNotInAdventurePreset());
|
||||
Assert.AreEqual(1, mock.RecommendationEventCount);
|
||||
}
|
||||
|
||||
static RecommendationTabView CreateViewWithMockedAnalytics(out MockThatCountsEvents mock)
|
||||
{
|
||||
mock = new MockThatCountsEvents();
|
||||
var view = new RecommendationTabView();
|
||||
view.RootVisualElement = new VisualElement();
|
||||
view.MultiplayerCenterAnalytics = mock;
|
||||
view.Refresh();
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36b51591e13f48b5bed734724d149fb4
|
||||
timeCreated: 1714672833
|
@@ -0,0 +1,360 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Unity.Multiplayer.Center.Common;
|
||||
using Unity.Multiplayer.Center.Questionnaire;
|
||||
using Unity.Multiplayer.Center.Recommendations;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Unity.MultiplayerCenterTests
|
||||
{
|
||||
/// <summary>
|
||||
/// This is an integration test based on known data. It ensures that the recommendation matches the expected results.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
partial class RecommendationTests
|
||||
{
|
||||
[SetUp]
|
||||
[TearDown]
|
||||
public void Setup()
|
||||
{
|
||||
Object.DestroyImmediate(RecommenderSystemDataObject.instance); // force reload from disk if accessed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the packages recommended for a given preset match the expected packages (4 players)
|
||||
/// Note that this does not handle hidden dependencies
|
||||
/// </summary>
|
||||
[TestCase(Preset.None)]
|
||||
[TestCase(Preset.Adventure,
|
||||
"com.unity.netcode.gameobjects",
|
||||
"com.unity.multiplayer.playmode",
|
||||
"com.unity.multiplayer.widgets",
|
||||
"com.unity.multiplayer.tools",
|
||||
"com.unity.dedicated-server",
|
||||
"com.unity.services.multiplayer",
|
||||
"com.unity.services.vivox")]
|
||||
[TestCase(Preset.Shooter,
|
||||
"com.unity.netcode",
|
||||
"com.unity.entities.graphics",
|
||||
"com.unity.multiplayer.widgets",
|
||||
"com.unity.multiplayer.playmode",
|
||||
"com.unity.services.multiplayer",
|
||||
"com.unity.services.vivox")]
|
||||
[TestCase(Preset.Racing,
|
||||
"com.unity.netcode",
|
||||
"com.unity.entities.graphics",
|
||||
"com.unity.multiplayer.widgets",
|
||||
"com.unity.multiplayer.playmode",
|
||||
"com.unity.services.multiplayer",
|
||||
"com.unity.services.vivox")]
|
||||
[TestCase(Preset.TurnBased,
|
||||
"com.unity.services.cloudcode",
|
||||
"com.unity.services.deployment",
|
||||
"com.unity.multiplayer.playmode",
|
||||
"com.unity.services.multiplayer",
|
||||
"com.unity.services.vivox")]
|
||||
[TestCase(Preset.Simulation,
|
||||
"com.unity.netcode.gameobjects",
|
||||
"com.unity.multiplayer.playmode",
|
||||
"com.unity.multiplayer.widgets",
|
||||
"com.unity.multiplayer.tools",
|
||||
"com.unity.services.multiplayer",
|
||||
"com.unity.services.vivox")]
|
||||
[TestCase(Preset.Strategy,
|
||||
"com.unity.multiplayer.playmode",
|
||||
"com.unity.services.multiplayer",
|
||||
"com.unity.transport",
|
||||
"com.unity.services.vivox")]
|
||||
[TestCase(Preset.Sports,
|
||||
"com.unity.netcode",
|
||||
"com.unity.entities.graphics",
|
||||
"com.unity.multiplayer.widgets",
|
||||
"com.unity.multiplayer.playmode",
|
||||
"com.unity.services.multiplayer",
|
||||
"com.unity.services.vivox")]
|
||||
[TestCase(Preset.RolePlaying,
|
||||
"com.unity.multiplayer.playmode",
|
||||
"com.unity.services.multiplayer",
|
||||
"com.unity.transport",
|
||||
"com.unity.services.vivox")]
|
||||
[TestCase(Preset.Async,
|
||||
"com.unity.services.cloudcode",
|
||||
"com.unity.services.deployment",
|
||||
"com.unity.services.vivox",
|
||||
"com.unity.services.multiplayer",
|
||||
"com.unity.multiplayer.playmode")]
|
||||
[TestCase(Preset.Fighting,
|
||||
"com.unity.multiplayer.playmode",
|
||||
"com.unity.services.multiplayer",
|
||||
"com.unity.transport",
|
||||
"com.unity.services.vivox")]
|
||||
[TestCase(Preset.Sandbox,
|
||||
"com.unity.netcode.gameobjects",
|
||||
"com.unity.multiplayer.playmode",
|
||||
"com.unity.multiplayer.widgets",
|
||||
"com.unity.multiplayer.tools",
|
||||
"com.unity.services.multiplayer",
|
||||
"com.unity.services.vivox")]
|
||||
public void TestPreset_RecommendedPackagesMatchesExpected(Preset preset, params string[] expected)
|
||||
{
|
||||
var recommendation = UtilsForRecommendationTests.ComputeRecommendationForPreset(preset);
|
||||
var allPackages = RecommenderSystem.GetSolutionsToRecommendedPackageViewData();
|
||||
if (expected == null || expected.Length == 0)
|
||||
{
|
||||
Assert.IsNull(recommendation);
|
||||
return;
|
||||
}
|
||||
|
||||
var actualRecommendedPackages = RecommendationUtils.PackagesToInstall(recommendation, allPackages)
|
||||
.Select(e => e.PackageId).ToArray();
|
||||
|
||||
// Use AreEqual instead of AreEquivalent to get a better error message
|
||||
Array.Sort(expected);
|
||||
Array.Sort(actualRecommendedPackages);
|
||||
CollectionAssert.AreEqual(expected, actualRecommendedPackages);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(AdventurePresetCases))]
|
||||
[TestCaseSource(nameof(SandboxPresetCases))]
|
||||
[TestCaseSource(nameof(AsyncPresetCases))]
|
||||
[TestCaseSource(nameof(TurnBasedPresetCases))]
|
||||
[TestCaseSource(nameof(FightingPresetCases))]
|
||||
[TestCaseSource(nameof(RacingPresetCases))]
|
||||
[TestCaseSource(nameof(RolePlayingPresetCases))]
|
||||
[TestCaseSource(nameof(ShooterPresetCases))]
|
||||
[TestCaseSource(nameof(SimulationPresetCases))]
|
||||
[TestCaseSource(nameof(StrategyPresetCases))]
|
||||
[TestCaseSource(nameof(SportPresetCases))]
|
||||
public void TestPreset_RecommendedSolutionsAreValid(string playerCount, PossibleSolution netcode, PossibleSolution hosting, Preset preset)
|
||||
{
|
||||
var recommendation = UtilsForRecommendationTests.ComputeRecommendationForPreset(preset, playerCount: playerCount);
|
||||
Assert.NotNull(recommendation);
|
||||
AssertTheRightSolutionsAreRecommended(netcode, hosting, recommendation);
|
||||
AssertAllDynamicReasonsAreProperlyFormed(recommendation);
|
||||
}
|
||||
|
||||
// First line of table for case "cheating prevention not so important"
|
||||
[TestCase(PossibleSolution.DA, PossibleSolution.NGO, "NoCost", "Slow", "2", "4", "8" )]
|
||||
[TestCase(PossibleSolution.DA, PossibleSolution.NGO,"NoCost", "Fast", "2", "4", "8" )]
|
||||
[TestCase(PossibleSolution.DA, PossibleSolution.NGO,"BestMargin", "Slow", "2", "4", "8" )]
|
||||
[TestCase(PossibleSolution.DA, PossibleSolution.NGO,"BestMargin", "Fast", "2", "4", "8" )]
|
||||
[TestCase(PossibleSolution.DA, PossibleSolution.NGO,"BestExperience", "Slow", "2", "4", "8")]
|
||||
[TestCase(PossibleSolution.DS, PossibleSolution.N4E,"BestExperience", "Fast", "2","4", "8")]
|
||||
|
||||
// Second line
|
||||
[TestCase(PossibleSolution.DA, PossibleSolution.NGO,"NoCost", "Slow", "16", "64+" )]
|
||||
[TestCase(PossibleSolution.DA, PossibleSolution.N4E,"NoCost", "Fast", "16", "64+" )]
|
||||
[TestCase(PossibleSolution.DA, PossibleSolution.NGO,"BestMargin", "Slow", "16", "64+" )]
|
||||
[TestCase(PossibleSolution.DS, PossibleSolution.N4E,"BestMargin", "Fast", "16", "64+" )]
|
||||
[TestCase(PossibleSolution.DS, PossibleSolution.N4E,"BestExperience", "Slow", "16", "64+")]
|
||||
[TestCase(PossibleSolution.DS, PossibleSolution.N4E,"BestExperience", "Fast", "16", "64+")]
|
||||
|
||||
// Third line
|
||||
[TestCase(PossibleSolution.DS, PossibleSolution.N4E,"NoCost", "Slow", "128", "256", "512" )]
|
||||
[TestCase(PossibleSolution.DS, PossibleSolution.N4E,"NoCost", "Fast", "128", "256", "512" )]
|
||||
[TestCase(PossibleSolution.DS, PossibleSolution.N4E,"BestMargin", "Slow", "128", "256", "512" )]
|
||||
[TestCase(PossibleSolution.DS, PossibleSolution.N4E,"BestMargin", "Fast", "128", "256", "512" )]
|
||||
[TestCase(PossibleSolution.DS, PossibleSolution.N4E,"BestExperience", "Slow", "128", "256", "512")]
|
||||
[TestCase(PossibleSolution.DS, PossibleSolution.N4E,"BestExperience", "Fast", "128", "256", "512")]
|
||||
public void TestGameSpecsForClientServerWithoutPreset_CheatingNotImportant_MatchesMiroTable(PossibleSolution expectedHosting,
|
||||
PossibleSolution expectedNetcode, string costSensitivity, string pace, params string[] playerCounts)
|
||||
{
|
||||
foreach (var playerCount in playerCounts)
|
||||
{
|
||||
var questionnaireData = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var answerData = GenerateAnswerDataForClientServer(false, pace, playerCount, costSensitivity);
|
||||
var recommendation = RecommenderSystem.GetRecommendation(questionnaireData, answerData);
|
||||
Assert.NotNull(recommendation);
|
||||
var msg = $"{pace}, {costSensitivity}, {playerCount} players: ";
|
||||
AssertTheRightSolutionsAreRecommended(expectedNetcode, expectedHosting, recommendation, msg);
|
||||
}
|
||||
}
|
||||
|
||||
// First line of table for case "cheating prevention important"
|
||||
[TestCase( PossibleSolution.NGO, "NoCost", "Slow", "2", "4", "8" )]
|
||||
[TestCase(PossibleSolution.N4E,"NoCost", "Fast", "2", "4", "8" )]
|
||||
[TestCase(PossibleSolution.NGO,"BestMargin", "Slow", "2", "4", "8" )]
|
||||
[TestCase(PossibleSolution.N4E,"BestMargin", "Fast", "2", "4", "8" )]
|
||||
[TestCase(PossibleSolution.NGO,"BestExperience", "Slow", "2", "4", "8")]
|
||||
[TestCase(PossibleSolution.N4E,"BestExperience", "Fast", "2", "4", "8")]
|
||||
|
||||
// Second line
|
||||
[TestCase(PossibleSolution.N4E,"NoCost", "Slow", "16", "64+" )]
|
||||
[TestCase(PossibleSolution.N4E,"NoCost", "Fast", "16", "64+" )]
|
||||
[TestCase(PossibleSolution.NGO,"BestMargin", "Slow", "16", "64+" )]
|
||||
[TestCase(PossibleSolution.N4E,"BestMargin", "Fast", "16", "64+" )]
|
||||
[TestCase(PossibleSolution.N4E,"BestExperience", "Slow", "16", "64+")]
|
||||
[TestCase(PossibleSolution.N4E,"BestExperience", "Fast", "16", "64+")]
|
||||
|
||||
// Third line
|
||||
[TestCase(PossibleSolution.N4E,"NoCost", "Slow", "128", "256", "512" )]
|
||||
[TestCase(PossibleSolution.N4E,"NoCost", "Fast", "128", "256", "512" )]
|
||||
[TestCase(PossibleSolution.N4E,"BestMargin", "Slow", "128", "256", "512" )]
|
||||
[TestCase(PossibleSolution.N4E,"BestMargin", "Fast", "128", "256", "512" )]
|
||||
[TestCase(PossibleSolution.N4E,"BestExperience", "Slow", "128", "256", "512")]
|
||||
[TestCase(PossibleSolution.N4E,"BestExperience", "Fast", "128", "256", "512")]
|
||||
public void TestGameSpecsForClientServerWithoutPreset_CheatingImportant_MatchesMiroTable(
|
||||
PossibleSolution expectedNetcode, string costSensitivity, string pace, params string[] playerCounts)
|
||||
{
|
||||
const PossibleSolution expectedHosting = PossibleSolution.DS; // for all cases
|
||||
foreach (var playerCount in playerCounts)
|
||||
{
|
||||
var questionnaireData = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var answerData = GenerateAnswerDataForClientServer(true, pace, playerCount, costSensitivity);
|
||||
var recommendation = RecommenderSystem.GetRecommendation(questionnaireData, answerData);
|
||||
Assert.NotNull(recommendation);
|
||||
var msg = $"{pace}, {costSensitivity}, {playerCount} players: ";
|
||||
AssertTheRightSolutionsAreRecommended(expectedNetcode, expectedHosting, recommendation, msg);
|
||||
}
|
||||
}
|
||||
|
||||
static AnswerData GenerateAnswerDataForClientServer(bool cheatingImportant, string pace, string playerCount, string costSensitivity)
|
||||
{
|
||||
return new AnswerData(){ Answers = new List<AnsweredQuestion>
|
||||
{
|
||||
new () { QuestionId = "CostSensitivity", Answers = new() {costSensitivity}},
|
||||
new () { QuestionId = "Pace", Answers = new() {pace}},
|
||||
new () { QuestionId = "PlayerCount", Answers = new() {playerCount}},
|
||||
new () { QuestionId = "NetcodeArchitecture", Answers = new() {"ClientServer"}},
|
||||
new () { QuestionId = "Cheating", Answers = new() {cheatingImportant ? "CheatingImportant" : "CheatingNotImportant"}}
|
||||
}};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PackageLists_PackagesHaveNames()
|
||||
{
|
||||
var allpackages = RecommenderSystemDataObject.instance.RecommenderSystemData.PackageDetailsById;
|
||||
foreach (var (id, details) in allpackages)
|
||||
{
|
||||
Assert.False(string.IsNullOrEmpty(details.Name), $"Package {id} has no name");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PackageLists_DependenciesAreAllValid()
|
||||
{
|
||||
var allpackages = RecommenderSystemDataObject.instance.RecommenderSystemData.PackageDetailsById;
|
||||
foreach (var (id, details) in allpackages)
|
||||
{
|
||||
Assert.NotNull(details, $"Package {id} has no details in RecommenderSystemData.PackageDetails");
|
||||
if(details.AdditionalPackages == null)
|
||||
continue;
|
||||
|
||||
foreach (var additionalPackageId in details.AdditionalPackages)
|
||||
{
|
||||
var additionalPackage = RecommendationUtils.GetPackageDetailForPackageId(additionalPackageId);
|
||||
Assert.NotNull(additionalPackage, $"Package {id} has an invalid dependency: {additionalPackageId}. It should be added to RecommenderSystemData.PackageDetails");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(PossibleSolution.NGO, PossibleSolution.LS, true)]
|
||||
[TestCase(PossibleSolution.NGO, PossibleSolution.DA, true)]
|
||||
[TestCase(PossibleSolution.NGO, PossibleSolution.DS, true)]
|
||||
[TestCase(PossibleSolution.NGO, PossibleSolution.CloudCode, true)] // ?
|
||||
|
||||
[TestCase(PossibleSolution.N4E, PossibleSolution.LS, true)]
|
||||
[TestCase(PossibleSolution.N4E, PossibleSolution.DA, false)]
|
||||
[TestCase(PossibleSolution.N4E, PossibleSolution.DS, true)]
|
||||
[TestCase(PossibleSolution.N4E, PossibleSolution.CloudCode, true)] // ?
|
||||
|
||||
[TestCase(PossibleSolution.CustomNetcode, PossibleSolution.LS, true)]
|
||||
[TestCase(PossibleSolution.CustomNetcode, PossibleSolution.DA, false)]
|
||||
[TestCase(PossibleSolution.CustomNetcode, PossibleSolution.DS, true)]
|
||||
[TestCase(PossibleSolution.CustomNetcode, PossibleSolution.CloudCode, true)] // ?
|
||||
|
||||
[TestCase(PossibleSolution.NoNetcode, PossibleSolution.LS, true)]
|
||||
[TestCase(PossibleSolution.NoNetcode, PossibleSolution.DA, false)] // ?
|
||||
[TestCase(PossibleSolution.NoNetcode, PossibleSolution.DS, true)]
|
||||
[TestCase(PossibleSolution.NoNetcode, PossibleSolution.CloudCode, true)]
|
||||
public void TestIncompatibilityWithSolution_MatchesExpected(PossibleSolution netcode, PossibleSolution hostingModel, bool expected)
|
||||
{
|
||||
var recommendationData = RecommenderSystemDataObject.instance.RecommenderSystemData;
|
||||
var actual = recommendationData.IsHostingModelCompatibleWithNetcode(netcode, hostingModel, out string reason);
|
||||
Assert.AreEqual(expected, actual, $"Hosting model {hostingModel} should be {(expected ? "compatible" : "incompatible")} with netcode {netcode}");
|
||||
Assert.AreEqual(expected, string.IsNullOrEmpty(reason), "reason is " + reason);
|
||||
}
|
||||
|
||||
// Additional packages are not used in version 1.0.0. We suspect however that killing the feature is not wise.
|
||||
// Therefore, we check that the logic is intact
|
||||
[Test]
|
||||
public void TestAdditionalPackagesStillWork()
|
||||
{
|
||||
var packageDetails = RecommenderSystemDataObject.instance.RecommenderSystemData.PackageDetailsById;
|
||||
|
||||
// nonsensical change, but with existing package.
|
||||
packageDetails["com.unity.netcode"].AdditionalPackages = new[] {"com.unity.netcode.gameobjects"};
|
||||
|
||||
var mainPackages = new List<RecommendedPackageViewData>
|
||||
{
|
||||
new (packageDetails["com.unity.netcode"], RecommendationType.MainArchitectureChoice, null),
|
||||
new (packageDetails["com.unity.services.multiplayer"], RecommendationType.HostingFeatured, null)
|
||||
};
|
||||
var expectedPackages = new List<string> {"com.unity.netcode", "com.unity.services.multiplayer",
|
||||
"com.unity.netcode.gameobjects", // added as additional package
|
||||
"com.unity.multiplayer.center.quickstart"}; // always added
|
||||
|
||||
RecommendationUtils.GetPackagesWithAdditionalPackages(mainPackages, out var allPackages, out var allNames, out var tooltip);
|
||||
expectedPackages.Sort();
|
||||
allPackages.Sort();
|
||||
CollectionAssert.AreEqual(expectedPackages, allPackages);
|
||||
|
||||
CollectionAssert.Contains(allNames, "Netcode for GameObjects");
|
||||
Assert.True(tooltip.Contains("Netcode for GameObjects"));
|
||||
|
||||
Assert.AreEqual(allPackages.Count -1, allNames.Count, "Quickstart should not be included in the names");
|
||||
}
|
||||
|
||||
static void AssertTheRightSolutionsAreRecommended(PossibleSolution expectedNetcode, PossibleSolution expectedHosting, RecommendationViewData recommendation, string msg=null)
|
||||
{
|
||||
AssertHighestScoreSolution(expectedNetcode, recommendation.NetcodeOptions, msg);
|
||||
AssertRightSolution(expectedNetcode, recommendation.NetcodeOptions, msg);
|
||||
if(expectedNetcode != PossibleSolution.NGO && expectedHosting == PossibleSolution.DA)
|
||||
AssertHighestScoreSolution(expectedHosting, recommendation.ServerArchitectureOptions, msg);
|
||||
else
|
||||
AssertRightSolution(expectedHosting, recommendation.ServerArchitectureOptions, msg);
|
||||
}
|
||||
|
||||
static void AssertAllDynamicReasonsAreProperlyFormed(RecommendationViewData recommendation)
|
||||
{
|
||||
foreach (var hostingModelRecommendation in recommendation.ServerArchitectureOptions)
|
||||
{
|
||||
AssertHasProperDynamicReason(hostingModelRecommendation);
|
||||
}
|
||||
|
||||
foreach (var netcodeRecommendation in recommendation.NetcodeOptions)
|
||||
{
|
||||
AssertHasProperDynamicReason(netcodeRecommendation);
|
||||
}
|
||||
}
|
||||
|
||||
static void AssertHasProperDynamicReason(RecommendedSolutionViewData solution)
|
||||
{
|
||||
Assert.False(solution.Reason.Contains(Scoring.DynamicKeyword), $"Reason for {solution.Solution} contain dynamic keyword");
|
||||
Assert.True(solution.Reason.Length > 10, $"Reason for {solution.Solution} is too short ({solution.Reason})");
|
||||
Assert.False(solution.Reason.EndsWith(".."), $"Reason for {solution.Solution} ends with two dots ({solution.Reason})");
|
||||
Assert.True(solution.Reason.EndsWith("."), $"Reason for {solution.Solution} does not end with a dot ({solution.Reason})");
|
||||
}
|
||||
|
||||
static void AssertRightSolution(PossibleSolution expectedNetcode, RecommendedSolutionViewData[] data, string msg="")
|
||||
{
|
||||
var selectedView = data.FirstOrDefault(e => e.Selected);
|
||||
Assert.NotNull(selectedView, $"{msg}No solution selected");
|
||||
Assert.AreEqual(expectedNetcode, selectedView.Solution,
|
||||
$"{msg}Expected {expectedNetcode} but got {selectedView.Solution} selected.{string.Join(", ", data.Select(e => $"{e.Solution}: {e.Score}"))}");
|
||||
}
|
||||
|
||||
static void AssertHighestScoreSolution(PossibleSolution expectedNetcode, RecommendedSolutionViewData[] data, string msg = "")
|
||||
{
|
||||
var maxScore = data.Max(e => e.Score);
|
||||
var solutionsWithMax = data.Where(e => Mathf.Approximately(e.Score, maxScore));
|
||||
Assert.AreEqual(1, solutionsWithMax.Count(), $"{msg}Multiple solutions with max score");
|
||||
var solutionWithMax = solutionsWithMax.First();
|
||||
Assert.True(solutionWithMax.RecommendationType is RecommendationType.MainArchitectureChoice or RecommendationType.Incompatible, $"The solution with the max score does not have the right recommendation type ({solutionWithMax.RecommendationType}");
|
||||
Assert.AreEqual(expectedNetcode, solutionWithMax.Solution, $"{msg}Expected {expectedNetcode} but highest score was {solutionWithMax.Solution}.{string.Join(", ", data.Select(e => $"{e.Solution}: {e.Score}"))}");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0efb92c924574ec59b71b9185a6ed8fb
|
||||
timeCreated: 1700485818
|
@@ -0,0 +1,237 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Basic test for the recommender system, without checking that the recommendation itself make sense.
|
||||
/// </summary>
|
||||
[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<ArgumentException>(() => 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<ArgumentException>(() => 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 284da2a38ea043d69e1a38fd8a22bec9
|
||||
timeCreated: 1700470994
|
@@ -0,0 +1,80 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Unity.Multiplayer.Center.Recommendations;
|
||||
using Unity.Multiplayer.Center.Window.UI.RecommendationView;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Unity.MultiplayerCenterTests
|
||||
{
|
||||
internal class SectionHeaderTests
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
var sectionHeader = new SectionHeader("Test");
|
||||
var dummyWindow = EditorWindow.GetWindow<DummyWindow>();
|
||||
dummyWindow.rootVisualElement.Clear();
|
||||
dummyWindow.rootVisualElement.Add(sectionHeader);
|
||||
}
|
||||
|
||||
SectionHeader GetSectionHeader()
|
||||
{
|
||||
return EditorWindow.GetWindow<DummyWindow>().rootVisualElement.Q<SectionHeader>();
|
||||
}
|
||||
|
||||
IEnumerator WaitUntilDropdownIsShown()
|
||||
{
|
||||
DropdownField dropDown = null;
|
||||
var maxWait = 100;
|
||||
|
||||
// wait for UI to be ready
|
||||
while (dropDown == null || maxWait-- > 0)
|
||||
{
|
||||
dropDown = GetSectionHeader().Q<DropdownField>();
|
||||
yield return null;
|
||||
}
|
||||
|
||||
Assert.NotNull(dropDown, "Dropdown could not be found");
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator SectionHeader_AppendsRecommendationText()
|
||||
{
|
||||
yield return WaitUntilDropdownIsShown();
|
||||
|
||||
var dropDown = GetSectionHeader().Q<DropdownField>();
|
||||
var recommendation = UtilsForRecommendationTests.GetSomeRecommendation();
|
||||
var serverChoices = recommendation.ServerArchitectureOptions;
|
||||
var serverChoice = serverChoices.First(sol => sol.RecommendationType == RecommendationType.MainArchitectureChoice);
|
||||
GetSectionHeader().UpdateData(recommendation.ServerArchitectureOptions);
|
||||
Assert.AreEqual(serverChoice.Title +" - Recommended", dropDown.value);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator SectionHeader_IgnoresIncompatibleSolutions()
|
||||
{
|
||||
yield return WaitUntilDropdownIsShown();
|
||||
|
||||
var dropDown = GetSectionHeader().Q<DropdownField>();
|
||||
var recommendation = UtilsForRecommendationTests.GetSomeRecommendation();
|
||||
var serverChoices = recommendation.ServerArchitectureOptions;
|
||||
serverChoices.Last().RecommendationType = RecommendationType.Incompatible;
|
||||
GetSectionHeader().UpdateData(recommendation.ServerArchitectureOptions);
|
||||
Assert.AreEqual(serverChoices.Count(sol => sol.RecommendationType !=RecommendationType.Incompatible), dropDown.choices.Count);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
var dummyWindow = EditorWindow.GetWindow<DummyWindow>();
|
||||
dummyWindow.rootVisualElement.Clear();
|
||||
dummyWindow.Close();
|
||||
}
|
||||
|
||||
class DummyWindow : EditorWindow { }
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6cfa81557736450abfb73264580451c1
|
||||
timeCreated: 1717420762
|
@@ -0,0 +1,111 @@
|
||||
using NUnit.Framework;
|
||||
using Unity.Multiplayer.Center.Analytics;
|
||||
using Unity.Multiplayer.Center.Common;
|
||||
using Unity.Multiplayer.Center.Common.Analytics;
|
||||
using UnityEngine;
|
||||
using Unity.Multiplayer.Center.Window;
|
||||
using UnityEditor;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Unity.MultiplayerCenterTests
|
||||
{
|
||||
[TestFixture]
|
||||
class TabGroupTests
|
||||
{
|
||||
TabGroup m_TabGroup;
|
||||
RecommendationTabView m_RecommendationTabView;
|
||||
GettingStartedTabView m_GettingStartedTabView;
|
||||
|
||||
private class AnalyticsMock : IMultiplayerCenterAnalytics
|
||||
{
|
||||
public void SendInstallationEvent(AnswerData data, Preset preset, Package[] packages) {}
|
||||
|
||||
public void SendRecommendationEvent(AnswerData data, Preset preset){}
|
||||
|
||||
public void SendGettingStartedInteractionEvent(string targetPackageId, string sectionId, InteractionDataType type, string displayName) {}
|
||||
}
|
||||
|
||||
private class MockTabEnabled : ITabView
|
||||
{
|
||||
public string Name => "MockTabEnabled";
|
||||
public VisualElement RootVisualElement { get; set; }
|
||||
public void SetVisible(bool visible)
|
||||
{
|
||||
RootVisualElement.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
public void Refresh() { }
|
||||
|
||||
public void Clear() { }
|
||||
|
||||
public IMultiplayerCenterAnalytics MultiplayerCenterAnalytics { get; set; }
|
||||
}
|
||||
|
||||
private class MockTabDisabled : ITabView
|
||||
{
|
||||
public string Name => "MockTabDisbled";
|
||||
public VisualElement RootVisualElement { get; set; }
|
||||
public void SetVisible(bool visible)
|
||||
{
|
||||
RootVisualElement.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
public bool IsEnabled => false;
|
||||
|
||||
public void Refresh() { }
|
||||
|
||||
public void Clear() { }
|
||||
|
||||
public IMultiplayerCenterAnalytics MultiplayerCenterAnalytics { get; set; }
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
m_RecommendationTabView = new RecommendationTabView();
|
||||
m_GettingStartedTabView = new GettingStartedTabView();
|
||||
m_TabGroup = new TabGroup(new AnalyticsMock(), new ITabView[] {m_RecommendationTabView, m_GettingStartedTabView, new MockTabEnabled(), new MockTabDisabled()});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TabGroup_CreateTabs_4TabViews()
|
||||
{
|
||||
m_TabGroup.CreateTabs();
|
||||
Assert.AreEqual(4, m_TabGroup.ViewCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TabGroup_CreateTabs_SelectsTabFromUserPreferences()
|
||||
{
|
||||
m_TabGroup.CreateTabs();
|
||||
var currentTabFromEditorPrefs = EditorPrefs.GetInt(PlayerSettings.productName + "_MultiplayerCenter_TabIndex", 0);
|
||||
Assert.AreEqual(currentTabFromEditorPrefs, m_TabGroup.CurrentTab);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TabGroup_SelectDeactivatedTab_SelectsFirstTab()
|
||||
{
|
||||
m_TabGroup.CreateTabs();
|
||||
m_TabGroup.SetSelected(3);
|
||||
Assert.AreEqual(0, m_TabGroup.CurrentTab);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TabGroup_AnalyticsIsPropagatedToAllViews()
|
||||
{
|
||||
Assert.NotNull(m_RecommendationTabView.MultiplayerCenterAnalytics);
|
||||
Assert.NotNull(m_GettingStartedTabView.MultiplayerCenterAnalytics);
|
||||
Assert.AreEqual(m_RecommendationTabView.MultiplayerCenterAnalytics, m_GettingStartedTabView.MultiplayerCenterAnalytics);
|
||||
Assert.AreEqual(m_RecommendationTabView.MultiplayerCenterAnalytics.GetType(), typeof(AnalyticsMock));
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
if (m_TabGroup != null)
|
||||
{
|
||||
m_TabGroup.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 470a2d15035f44b7b2888a0db0704bff
|
||||
timeCreated: 1709910098
|
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "Unity.Multiplayer.Center.Editor.Tests",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"UnityEngine.TestRunner",
|
||||
"UnityEditor.TestRunner",
|
||||
"Unity.Multiplayer.Center.Editor",
|
||||
"Unity.Multiplayer.Center.Common",
|
||||
"Unity.Multiplayer.Center.Integrations"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"nunit.framework.dll"
|
||||
],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 787ec048daec145b580d1134da5dd278
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using Unity.Multiplayer.Center.Common;
|
||||
using Unity.Multiplayer.Center.Questionnaire;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.MultiplayerCenterTests
|
||||
{
|
||||
[TestFixture]
|
||||
internal class UserChoicesMigrationTests
|
||||
{
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetup()
|
||||
{
|
||||
// Copy user choices to temp file to restore after tests.
|
||||
UtilsForMultiplayerCenterTests.CopyUserChoicesToTempFile();
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void OneTimeTearDown()
|
||||
{
|
||||
// Restore user choices after tests.
|
||||
UtilsForMultiplayerCenterTests.RestoreUserChoicesFromTempFile();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMigration_Pre1_2To1_2_RemovesCompetitivenessButNothingElse()
|
||||
{
|
||||
var questionnaire = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
questionnaire.Version = "1.2";
|
||||
var userAnswers = new AnswerData()
|
||||
{
|
||||
Answers = new List<AnsweredQuestion>()
|
||||
{
|
||||
new () { QuestionId = "CostSensitivity", Answers = new List<string>() {"BestMargin"}},
|
||||
new () { QuestionId = "Pace", Answers = new List<string>() {"Slow"}},
|
||||
new () { QuestionId = "Competitiveness", Answers = new List<string>() {"Competitive"}},
|
||||
new () { QuestionId = "PlayerCount", Answers = new List<string>() {"2"}}
|
||||
}
|
||||
};
|
||||
UserChoicesObject.instance.UserAnswers = userAnswers;
|
||||
UserChoicesObject.instance.QuestionnaireVersion = null;
|
||||
|
||||
var foundCompetitive = Logic.TryGetAnswerByQuestionId(userAnswers, "Competitiveness", out var answer);
|
||||
Assert.True(foundCompetitive);
|
||||
Assert.NotNull(answer);
|
||||
|
||||
var errorsBefore = Logic.ValidateAnswers(questionnaire, UserChoicesObject.instance.UserAnswers);
|
||||
Assert.IsNotEmpty(errorsBefore);
|
||||
|
||||
Logic.MigrateUserChoices(questionnaire, UserChoicesObject.instance);
|
||||
|
||||
var foundCompetitiveAfterMigration = Logic.TryGetAnswerByQuestionId(userAnswers, "Competitiveness", out var answerAfterMigration);
|
||||
Assert.False(foundCompetitiveAfterMigration);
|
||||
Assert.Null(answerAfterMigration);
|
||||
|
||||
Assert.NotNull(UserChoicesObject.instance.QuestionnaireVersion);
|
||||
|
||||
Assert.AreEqual(3, UserChoicesObject.instance.UserAnswers.Answers.Count);
|
||||
|
||||
var errors = Logic.ValidateAnswers(questionnaire, UserChoicesObject.instance.UserAnswers);
|
||||
Assert.IsEmpty(errors);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMigration_1_2To1_3_ChangesMediumPaceToSlow()
|
||||
{
|
||||
var questionnaire = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
questionnaire.Version = "1.3";
|
||||
var userAnswers = new AnswerData()
|
||||
{
|
||||
Answers = new List<AnsweredQuestion>()
|
||||
{
|
||||
new () { QuestionId = "CostSensitivity", Answers = new List<string>() {"BestMargin"}},
|
||||
new () { QuestionId = "Pace", Answers = new List<string>() {"Medium"}},
|
||||
new () { QuestionId = "PlayerCount", Answers = new List<string>() {"2"}}
|
||||
}
|
||||
};
|
||||
UserChoicesObject.instance.UserAnswers = userAnswers;
|
||||
UserChoicesObject.instance.QuestionnaireVersion = "1.2";
|
||||
|
||||
var foundPace = Logic.TryGetAnswerByQuestionId(userAnswers, "Pace", out var answer);
|
||||
Assert.True(foundPace);
|
||||
Assert.NotNull(answer);
|
||||
|
||||
Logic.MigrateUserChoices(questionnaire, UserChoicesObject.instance);
|
||||
|
||||
var foundPaceAfterMigration = Logic.TryGetAnswerByQuestionId(userAnswers, "Pace", out var answerAfterMigration);
|
||||
Assert.True(foundPaceAfterMigration);
|
||||
Assert.NotNull(answerAfterMigration);
|
||||
Assert.AreEqual("Slow", answerAfterMigration.Answers[0]);
|
||||
|
||||
var errors = Logic.ValidateAnswers(questionnaire, UserChoicesObject.instance.UserAnswers);
|
||||
Assert.IsEmpty(errors);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestMigration_SameVersion_RemovesNothing()
|
||||
{
|
||||
var questionnaire = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var userAnswers = new AnswerData()
|
||||
{
|
||||
Answers = new List<AnsweredQuestion>()
|
||||
{
|
||||
new () { QuestionId = "CostSensitivity", Answers = new List<string>() {"BestMargin"}},
|
||||
new () { QuestionId = "Pace", Answers = new List<string>() {"Slow"}},
|
||||
new () { QuestionId = "PlayerCount", Answers = new List<string>() {"2"}}
|
||||
}
|
||||
};
|
||||
UserChoicesObject.instance.UserAnswers = userAnswers;
|
||||
UserChoicesObject.instance.QuestionnaireVersion = "1.3";
|
||||
|
||||
Logic.MigrateUserChoices(questionnaire, UserChoicesObject.instance);
|
||||
|
||||
Assert.AreEqual("1.3", UserChoicesObject.instance.QuestionnaireVersion);
|
||||
|
||||
Assert.AreEqual(3, UserChoicesObject.instance.UserAnswers.Answers.Count);
|
||||
|
||||
var errors = Logic.ValidateAnswers(questionnaire, UserChoicesObject.instance.UserAnswers);
|
||||
Assert.IsEmpty(errors);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15513aed5d41411c887ccfe14c103337
|
||||
timeCreated: 1715328871
|
@@ -0,0 +1,70 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Unity.Multiplayer.Center.Recommendations;
|
||||
|
||||
namespace Unity.MultiplayerCenterTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Cheap checks on some user visible text.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
internal class UserVisibleTextTests
|
||||
{
|
||||
static readonly string[] k_Verbs = {"is", "offers", "can", "costs", "would", "should", "works", "tends", "enables"};
|
||||
|
||||
[Test]
|
||||
public void AllScoreImpacts_ShouldHaveANonEmptyReason()
|
||||
{
|
||||
var questionnaireData = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
foreach (var question in questionnaireData.Questions)
|
||||
{
|
||||
foreach (var answer in question.Choices)
|
||||
{
|
||||
for (var index = 0; index < answer.ScoreImpacts.Length; index++)
|
||||
{
|
||||
var scoreImpact = answer.ScoreImpacts[index];
|
||||
Assert.False(string.IsNullOrEmpty(scoreImpact.Comment),
|
||||
$"Comment is empty for question {question.Id} answer {answer.Id} impact at index {index}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AllScoreImpacts_StartWithAVerbAndDoNotEndWithADot()
|
||||
{
|
||||
var questionnaireData = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
foreach (var question in questionnaireData.Questions)
|
||||
{
|
||||
foreach (var answer in question.Choices)
|
||||
{
|
||||
for (var index = 0; index < answer.ScoreImpacts.Length; index++)
|
||||
{
|
||||
var comment = answer.ScoreImpacts[index].Comment;
|
||||
var firstWord = comment.Split(' ')[0];
|
||||
CollectionAssert.Contains(k_Verbs, firstWord,
|
||||
$"Comment '{comment}' does not start with a verb for question {question.Id} answer {answer.Id} impact at index {index}");
|
||||
Assert.False(comment.EndsWith("."),
|
||||
$"Comment '{comment}' should not end with a dot for question {question.Id} answer {answer.Id} impact at index {index}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AllSolutionsData_DoNotHaveAVerbBeforeDynamicText()
|
||||
{
|
||||
var data = RecommenderSystemDataObject.instance.RecommenderSystemData;
|
||||
const string dynamicKeyword = Scoring.DynamicKeyword;
|
||||
foreach (var solution in data.RecommendedSolutions)
|
||||
{
|
||||
Assert.True(solution.ShortDescription.Contains(dynamicKeyword),
|
||||
$"Solution {solution.Type} description does not contain dynamic text '{dynamicKeyword}'");
|
||||
var wordBeforeDynamic = solution.ShortDescription.Split(dynamicKeyword)[0].Split(' ').Last();
|
||||
Assert.False(k_Verbs.Contains(wordBeforeDynamic),
|
||||
$"Solution {solution.Type} description starts with a verb '{wordBeforeDynamic}' before dynamic text '{dynamicKeyword}'");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3d3226643b94888b02e25de0a9de55f
|
||||
timeCreated: 1716996547
|
@@ -0,0 +1,79 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Unity.Multiplayer.Center.Window;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Unity.MultiplayerCenterTests
|
||||
{
|
||||
internal static class UtilsForGettingStartedTabTests
|
||||
{
|
||||
public static void OpenGettingStartedTab()
|
||||
{
|
||||
var window = EditorWindow.GetWindow<MultiplayerCenterWindow>();
|
||||
window.CurrentTabTest = 1;
|
||||
}
|
||||
|
||||
public static VisualElement GetSection(string sectionName)
|
||||
{
|
||||
return EditorWindow.GetWindow<MultiplayerCenterWindow>().rootVisualElement.Q(sectionName);
|
||||
}
|
||||
|
||||
public static bool CheckErrorLogged(LogType type)
|
||||
{
|
||||
return type == LogType.Error || type == LogType.Exception;
|
||||
}
|
||||
|
||||
public static bool DeleteSetupDirectoryIfExists(string directoryPath)
|
||||
{
|
||||
directoryPath = directoryPath.TrimEnd('/');
|
||||
var metaFilePath = $"{directoryPath}.meta";
|
||||
if(File.Exists(metaFilePath)) // In case the directory is not accessible, Delete will throw an exception
|
||||
{
|
||||
File.Delete(metaFilePath);
|
||||
}
|
||||
|
||||
if (Directory.Exists(directoryPath))
|
||||
{
|
||||
Directory.Delete(directoryPath, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void AssertGameObjectHasNoMissingScripts(GameObject gameObject)
|
||||
{
|
||||
var components = gameObject.GetComponents<Component>();
|
||||
for (var index = 0; index < components.Length; index++)
|
||||
{
|
||||
var component = components[index];
|
||||
Assert.IsNotNull(component, $"GameObject {gameObject.name} has missing script (component index: {index})");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a SettingsPage (right side of the SettingsWindow) has content.
|
||||
/// This is useful to test if calling SettingsService.OpenProjectSettings("Your setting")
|
||||
/// makes the window show content. In case your path does not open settings, the right side
|
||||
/// will be empty and false will be returned.
|
||||
/// Caution: This will only work for SettingsPages created with UI-Toolkit.
|
||||
/// </summary>
|
||||
/// <returns> True if settings are shown on the right side of the SettingsWindow, false if no settings window is
|
||||
/// shown, or the content on the right is empty.</returns>
|
||||
public static bool SettingsWindowRightSideHasContent()
|
||||
{
|
||||
var windows = Resources.FindObjectsOfTypeAll(typeof(EditorWindow)) as EditorWindow[];
|
||||
var projectSettingsWindow = windows.FirstOrDefault(window => window.GetType().FullName == "UnityEditor.ProjectSettingsWindow");
|
||||
if (projectSettingsWindow == null)
|
||||
return false;
|
||||
|
||||
var settingsPanel = projectSettingsWindow.rootVisualElement.Q<VisualElement>(className: "settings-panel");
|
||||
if (settingsPanel == null)
|
||||
return false;
|
||||
return settingsPanel.Children().First().childCount > 0;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c37c4c45cacc46edb43b8c32617b3e34
|
||||
timeCreated: 1715074033
|
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using Unity.Multiplayer.Center.Common;
|
||||
using Unity.Multiplayer.Center.Questionnaire;
|
||||
using Unity.Multiplayer.Center.Window;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Unity.MultiplayerCenterTests
|
||||
{
|
||||
/// <summary>
|
||||
/// This is meant to be a utility class for MultiplayerCenter tests (including the project tests).
|
||||
/// </summary>
|
||||
internal static class UtilsForMultiplayerCenterTests
|
||||
{
|
||||
public static void CloseMultiplayerCenterWindow()
|
||||
{
|
||||
var allWindows = Resources.FindObjectsOfTypeAll<MultiplayerCenterWindow>();
|
||||
if (allWindows.Length <= 0) return;
|
||||
foreach (var window in allWindows)
|
||||
{
|
||||
window.Close();
|
||||
UnityEngine.Object.DestroyImmediate(window);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ResetUserChoices()
|
||||
{
|
||||
UserChoicesObject.instance.UserAnswers = new AnswerData();
|
||||
UserChoicesObject.instance.Preset = new Preset();
|
||||
UserChoicesObject.instance.SelectedSolutions = new SelectedSolutionsData();
|
||||
UserChoicesObject.instance.Save();
|
||||
}
|
||||
|
||||
public static void PopulateUserAnswersForPresetAndPlayerCount(Preset preset, int playerCount)
|
||||
{
|
||||
//TODO: also assert player count has proper value
|
||||
Assert.AreNotEqual(Preset.None, preset);
|
||||
UserChoicesObject.instance.Preset = preset;
|
||||
Logic.Update(UserChoicesObject.instance.UserAnswers, CreatePlayerCountAnswer(playerCount));
|
||||
UserChoicesObject.instance.Save();
|
||||
|
||||
var (resultAnswerData, recommendation) = Logic.ApplyPresetToAnswerData(
|
||||
UserChoicesObject.instance.UserAnswers, preset, QuestionnaireObject.instance.Questionnaire);
|
||||
|
||||
Assert.NotNull(recommendation);
|
||||
|
||||
UserChoicesObject.instance.UserAnswers = resultAnswerData;
|
||||
UserChoicesObject.instance.Save();
|
||||
}
|
||||
|
||||
public static AnsweredQuestion CreatePlayerCountAnswer(int playerCount)
|
||||
{
|
||||
// simplified validation for the sake of the test
|
||||
Assert.True(playerCount is 2 or 4 or 8, "Please use a player count of 2, 4 or 8.");
|
||||
return new AnsweredQuestion() {QuestionId = "PlayerCount", Answers = new() {playerCount.ToString()}};
|
||||
}
|
||||
|
||||
public static AnsweredQuestion GetAnsweredQuestionThatIsNotInAdventurePreset()
|
||||
{
|
||||
// Corresponds to answer "I do not know" for the question "Netcode Architecture"
|
||||
return new AnsweredQuestion() { QuestionId = "NetcodeArchitecture", Answers = new() {"NoNetcode"}};
|
||||
}
|
||||
|
||||
// Copy the UserChoices file to a temp file to be able to restore it after the tests.
|
||||
public static void CopyUserChoicesToTempFile()
|
||||
{
|
||||
var sourceFilePath = GetUserChoicesFullFilePath();
|
||||
var tempFilePath = GetUserChoicesTempFilePath();
|
||||
|
||||
// Can happen if the there are no UserChoices saved to disk yet.
|
||||
if (!File.Exists(sourceFilePath))
|
||||
{
|
||||
// Save to have something to copy.
|
||||
UserChoicesObject.instance.Save();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.Copy(sourceFilePath, tempFilePath, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.Fail($"Could not create Temp File from {sourceFilePath} {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the UserChoices file from the temp file and delete the temp file.
|
||||
public static void RestoreUserChoicesFromTempFile()
|
||||
{
|
||||
var sourceFilePath = GetUserChoicesFullFilePath();
|
||||
var tempFilePath = GetUserChoicesTempFilePath();
|
||||
|
||||
try
|
||||
{
|
||||
File.Copy(tempFilePath,sourceFilePath, true);
|
||||
File.Delete(tempFilePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.Fail($"Could not restore UserChoices from temp file {tempFilePath}, or could not delete temp file {tempFilePath} {e.Message}");
|
||||
}
|
||||
Object.DestroyImmediate(UserChoicesObject.instance);
|
||||
}
|
||||
|
||||
public static void OpenTabByIndex(int tabIndex)
|
||||
{
|
||||
var multiplayerWindow = EditorWindow.GetWindow<MultiplayerCenterWindow>();
|
||||
multiplayerWindow.CurrentTabTest = tabIndex;
|
||||
}
|
||||
|
||||
public static void SetNetcodeSolutionToCustomNetcode(bool flag)
|
||||
{
|
||||
UserChoicesObject.instance.SelectedSolutions.SelectedNetcodeSolution = flag ? SelectedSolutionsData.NetcodeSolution.CustomNetcode : SelectedSolutionsData.NetcodeSolution.NGO;
|
||||
}
|
||||
|
||||
public static void SetSelectedSolutionToDistributedAuthority(bool flag)
|
||||
{
|
||||
if (!flag)
|
||||
{
|
||||
UserChoicesObject.instance.SelectedSolutions.SelectedHostingModel = SelectedSolutionsData.HostingModel.ClientHosted;
|
||||
return;
|
||||
}
|
||||
|
||||
UserChoicesObject.instance.SelectedSolutions.SelectedNetcodeSolution = SelectedSolutionsData.NetcodeSolution.NGO;
|
||||
UserChoicesObject.instance.SelectedSolutions.SelectedHostingModel = SelectedSolutionsData.HostingModel.DistributedAuthority;
|
||||
}
|
||||
|
||||
static string GetUserChoicesFullFilePath()
|
||||
{
|
||||
var choicesObject = UserChoicesObject.instance;
|
||||
var dirName = Path.GetDirectoryName(Application.dataPath);
|
||||
return Path.Combine(dirName, choicesObject.FilePath);
|
||||
}
|
||||
|
||||
static string GetUserChoicesTempFilePath()
|
||||
{
|
||||
return Path.ChangeExtension(GetUserChoicesFullFilePath(), ".tmp");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfc5ccd4416341629caf73dd3e318aac
|
||||
timeCreated: 1714987913
|
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Unity.Multiplayer.Center.Common;
|
||||
using Unity.Multiplayer.Center.Questionnaire;
|
||||
using Unity.Multiplayer.Center.Recommendations;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.MultiplayerCenterTests
|
||||
{
|
||||
internal static class UtilsForRecommendationTests
|
||||
{
|
||||
public static QuestionnaireData GetProjectQuestionnaire()
|
||||
{
|
||||
// TODO: maybe accessing from disk is a better idea than using the singleton? (side effects)
|
||||
var result = Clone(QuestionnaireObject.instance.Questionnaire);
|
||||
// sanity checks
|
||||
Assert.IsNotNull(result);
|
||||
Assert.IsNotNull(result.Questions);
|
||||
Assert.Greater(result.Questions.Length, 0);
|
||||
Assert.False(result.Questions.Any(x => x.Choices.Length == 0));
|
||||
return result;
|
||||
}
|
||||
|
||||
public static AnswerData BuildAnswerMatching(QuestionnaireData questionnaireData)
|
||||
{
|
||||
var answerData = new AnswerData();
|
||||
foreach (var question in questionnaireData.Questions)
|
||||
{
|
||||
var middleChoice = question.Choices[question.Choices.Length / 2 - 1];
|
||||
var answeredQuestion = new AnsweredQuestion()
|
||||
{
|
||||
QuestionId = question.Id,
|
||||
Answers = new(){middleChoice.Id}
|
||||
};
|
||||
Logic.Update(answerData, answeredQuestion);
|
||||
}
|
||||
|
||||
return answerData;
|
||||
}
|
||||
|
||||
public static RecommendationViewData GetSomeRecommendation()
|
||||
{
|
||||
var questionnaireData = GetProjectQuestionnaire();
|
||||
var answerData = BuildAnswerMatching(questionnaireData);
|
||||
return RecommenderSystem.GetRecommendation(questionnaireData, answerData);
|
||||
}
|
||||
|
||||
public static void AssertRecommendedSolutionNotNull(RecommendedSolutionViewData solution, bool checkMainPackage = true)
|
||||
{
|
||||
Assert.NotNull(solution);
|
||||
Assert.False(string.IsNullOrEmpty(solution.Title));
|
||||
Assert.That(solution.RecommendationType is RecommendationType.MainArchitectureChoice
|
||||
or RecommendationType.SecondArchitectureChoice or RecommendationType.NotRecommended or RecommendationType.Incompatible, $"Recommendation type: {solution.RecommendationType}");
|
||||
if (checkMainPackage)
|
||||
Assert.NotNull(solution.MainPackage);
|
||||
}
|
||||
|
||||
public static void AssertAllRecommendedPackageNotNull(SolutionsToRecommendedPackageViewData allPackages)
|
||||
{
|
||||
foreach (var selection in allPackages.Selections)
|
||||
{
|
||||
var packages = allPackages.GetPackagesForSelection(selection);
|
||||
var selectionString = $"Netcode {selection.Netcode} - Hosting {selection.HostingModel}";
|
||||
Assert.NotNull(packages, selectionString);
|
||||
CollectionAssert.IsNotEmpty(packages, selectionString);
|
||||
foreach (var package in packages)
|
||||
{
|
||||
Assert.False(string.IsNullOrEmpty(package.Name), selectionString);
|
||||
Assert.NotNull(string.IsNullOrEmpty(package.PackageId), $"{selectionString} - {package.Name}");
|
||||
Assert.That(package.RecommendationType != RecommendationType.MainArchitectureChoice &&
|
||||
package.RecommendationType != RecommendationType.SecondArchitectureChoice, $"{selectionString} - {package.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All solutions should mention all packages, however the actual recommendations (RecommendationType) should be different.
|
||||
/// This checks that all packages in solution 1 are also in solution 2, but that they don't all have the same recommendation.
|
||||
/// Note: this a current requirement, but this might evolve. Adapt if necessary.
|
||||
/// 2nd Note: With the introduction of the Multiplayer SDK this requirement changed for the server architecture options, but not for the netcode options.
|
||||
/// </summary>
|
||||
/// <param name="reco1">Solution 1</param>
|
||||
/// <param name="reco2">Solution 2</param>
|
||||
public static void AssertSamePackagesWithDifferentRecommendations(RecommendedPackageViewData[] reco1, RecommendedPackageViewData[] reco2, string msg)
|
||||
{
|
||||
Assert.AreEqual(reco1.Length, reco2.Length);
|
||||
var allTheSame = true;
|
||||
foreach (var p in reco1)
|
||||
{
|
||||
var matching = reco2.FirstOrDefault(x => x.PackageId == p.PackageId);
|
||||
Assert.IsNotNull(matching);
|
||||
if(matching.RecommendationType != p.RecommendationType)
|
||||
allTheSame = false;
|
||||
}
|
||||
Assert.False(allTheSame, $"The two solutions have exactly the same recommended packages!");
|
||||
}
|
||||
|
||||
public static T Clone<T>(T obj)
|
||||
{
|
||||
return JsonUtility.FromJson<T>(JsonUtility.ToJson(obj));
|
||||
}
|
||||
|
||||
public static RecommendationViewData ComputeRecommendationForPreset(Preset preset, string playerCount = "4")
|
||||
{
|
||||
var questionnaireData = UtilsForRecommendationTests.GetProjectQuestionnaire();
|
||||
var answerData = GetAnswerDataForPreset(questionnaireData, preset, playerCount);
|
||||
var recommendation = RecommenderSystem.GetRecommendation(questionnaireData, answerData);
|
||||
return recommendation;
|
||||
}
|
||||
|
||||
public static AnswerData GetAnswerDataForPreset(QuestionnaireData questionnaireData, Preset preset, string playerCount)
|
||||
{
|
||||
if (preset == Preset.None)
|
||||
return new AnswerData();
|
||||
|
||||
var indexOfPreset = Array.IndexOf(questionnaireData.PresetData.Presets, preset);
|
||||
var matchingAnswers = questionnaireData.PresetData.Answers[indexOfPreset].Clone();
|
||||
var playerCountAnswer = new AnsweredQuestion()
|
||||
{
|
||||
QuestionId = "PlayerCount",
|
||||
Answers = new() {playerCount}
|
||||
};
|
||||
Logic.Update(matchingAnswers, playerCountAnswer);
|
||||
return matchingAnswers;
|
||||
}
|
||||
|
||||
public static void SimulateSelectionChange(PossibleSolution newSelectedSolution, RecommendedSolutionViewData[] solutionViewDatas)
|
||||
{
|
||||
foreach (var solution in solutionViewDatas)
|
||||
{
|
||||
solution.Selected = solution.Solution == newSelectedSolution;
|
||||
if(solution.Selected && solution.RecommendationType == RecommendationType.Incompatible)
|
||||
Assert.Fail("Selected incompatible solution");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca560c7d2a0d4453a50ec4f704ce9ce1
|
||||
timeCreated: 1700473715
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e4d240cf158245a9945c4df01d83bc1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,33 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using NUnit.Framework;
|
||||
using System.Collections;
|
||||
#if ENABLE_INPUT_SYSTEM && NEW_INPUT_SYSTEM_INSTALLED
|
||||
using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
// This package does not contain any runtim components, therefore
|
||||
// this test is only a placeholder.
|
||||
|
||||
|
||||
[TestFixture]
|
||||
class RuntimeExampleTest {
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM && NEW_INPUT_SYSTEM_INSTALLED
|
||||
if (Keyboard.current == null)
|
||||
{
|
||||
InputSystem.AddDevice<Keyboard>();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PlayModeSampleTestSimplePasses()
|
||||
{
|
||||
// Use the Assert class to test conditions.
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7456ea4ba2dcd48d3909b82e610f2048
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "Unity.Multiplayer.Center.Tests",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"UnityEngine.TestRunner",
|
||||
"UnityEditor.TestRunner",
|
||||
"Unity.InputSystem"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"nunit.framework.dll"
|
||||
],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.inputsystem",
|
||||
"expression": "1.4.0",
|
||||
"define": "NEW_INPUT_SYSTEM_INSTALLED"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2664430aff4254d79887d32c3fc1e221
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user