public async Task DeployGamePopulatesDeploymentModeAsync( [Values(DeployOnLaunchSetting.DELTA, DeployOnLaunchSetting.ALWAYS)] DeployOnLaunchSetting value, [Values] bool commandSucceeds) { string localPath = GetLocalPath(); IAsyncProject project = GetProjectWithLocalPathAndDeployMode(localPath, value); (SshTarget target, IAction action, ICancelable cancelable) = GetDeploymentArguments(); (_, IRemoteCommand command, IRemoteDeploy deploy, _) = GetTestObjects(); if (!commandSucceeds) { command.RunWithSuccessAsync(Arg.Any <SshTarget>(), Arg.Any <string>()) .Returns(_ => throw new ProcessException(_chmodFailed)); } try { await deploy.DeployGameExecutableAsync(project, target, cancelable, action); } catch (DeployException) { Assert.IsFalse(commandSucceeds); } CopyBinaryData actionEvent = action.GetEvent().CopyExecutable; Assert.That(actionEvent.DeploymentMode, Is.EqualTo(CopyBinaryType.Types.DeploymentMode.GgpRsync)); }
public void DeployGameExecutablePopulatesActionEventOnFailureInDeployment( [Values(DeployOnLaunchSetting.DELTA, DeployOnLaunchSetting.ALWAYS)] DeployOnLaunchSetting value) { string localPath = GetLocalPath(); IAsyncProject project = GetProjectWithLocalPathAndDeployMode(localPath, value); (SshTarget target, IAction action, ICancelable cancelable) = GetDeploymentArguments(); (IRemoteFile file, _, IRemoteDeploy deploy, _) = GetTestObjects(); file .SyncAsync(Arg.Any <SshTarget>(), Arg.Any <string>(), Arg.Any <string>(), Arg.Any <ICancelable>(), Arg.Any <bool>()) .Returns <Task>(_ => throw new ProcessException(_rsyncFailed)); var error = Assert.ThrowsAsync <DeployException>( async() => await deploy.DeployGameExecutableAsync( project, target, cancelable, action)); Assert.Multiple(() => { string expectedError = ErrorStrings.FailedToDeployExecutable(_rsyncFailed); Assert.That(error.Message, Is.EqualTo(expectedError)); CopyBinaryData actionEvent = action.GetEvent().CopyExecutable; Assert.NotNull(actionEvent); Assert.NotNull(actionEvent.CopyBinaryBytes); Assert.IsTrue(actionEvent.CopyAttempted); Assert.That(actionEvent.CopyExitCode, Is.EqualTo(-1)); Assert.IsNull(actionEvent.SshChmodExitCode); }); }
IAsyncProject GetProjectWithLocalPathAndDeployMode(string localPath, DeployOnLaunchSetting deployOnLaunchSetting) { var project = Substitute.For <IAsyncProject>(); project.GetDeployOnLaunchAsync().Returns(deployOnLaunchSetting); project.GetTargetPathAsync().Returns(localPath); return(project); }
public async Task GetGgpDeployOnLaunchAsync(string stringValue, DeployOnLaunchSetting enumValue) { var projectPath = @"C:\GGP_project_path\"; var projectValues = new ProjectValues { GgpDeployOnLaunch = stringValue, }; var configuredProject = CreateConfiguredProject(projectValues, projectPath); IAsyncProject project = new ConfiguredProjectAdapter(configuredProject); Assert.AreEqual(enumValue, await project.GetDeployOnLaunchAsync()); }
public async Task DeployGamePopulatesSignatureCheckModeAsync( DeployOnLaunchSetting deploySetting, BinarySignatureCheck.Types.Result signatureCheck) { string localPath = GetLocalPath(); IAsyncProject project = GetProjectWithLocalPathAndDeployMode(localPath, deploySetting); (SshTarget target, IAction action, ICancelable cancelable) = GetDeploymentArguments(); (_, _, IRemoteDeploy deploy, _) = GetTestObjects(); await deploy.DeployGameExecutableAsync(project, target, cancelable, action); CopyBinaryData actionEvent = action.GetEvent().CopyExecutable; Assert.That(actionEvent.SignatureCheckResult, Is.EqualTo(signatureCheck)); }
string GgpDeployOnLaunchToDisplayName(DeployOnLaunchSetting enumValue) { // These values are copied from DisplayNames in debugger_ggp.xml. switch (enumValue) { case DeployOnLaunchSetting.FALSE: return("No"); case DeployOnLaunchSetting.DELTA: return("Yes - incremental"); case DeployOnLaunchSetting.ALWAYS: return("Yes - always"); } return(""); }
public async Task DeployGameExecutablePopulatesActionEventOnSuccessAsync( [Values(DeployOnLaunchSetting.DELTA, DeployOnLaunchSetting.ALWAYS)] DeployOnLaunchSetting value) { string localPath = GetLocalPath(); IAsyncProject project = GetProjectWithLocalPathAndDeployMode(localPath, value); (SshTarget target, IAction action, ICancelable cancelable) = GetDeploymentArguments(); (_, _, IRemoteDeploy deploy, _) = GetTestObjects(); await deploy.DeployGameExecutableAsync(project, target, cancelable, action); Assert.Multiple(() => { CopyBinaryData actionEvent = action.GetEvent().CopyExecutable; Assert.NotNull(actionEvent); Assert.NotNull(actionEvent.CopyBinaryBytes); Assert.IsTrue(actionEvent.CopyAttempted); Assert.That(actionEvent.CopyExitCode, Is.EqualTo(0)); Assert.That(actionEvent.SshChmodExitCode, Is.EqualTo(0)); }); }
/// <summary> /// Selects a gamelet from the given list and prepares it for running a game. /// </summary> /// <exception cref="InvalidStateException"> /// Thrown when the selected gamelet is in an unexpected state.</exception> /// <exception cref="ConfigurationException"> /// Thrown if there is no gamelet reserved</exception> /// <exception cref="CloudException">Thrown if there are any RPC errors.</exception> /// <returns>True if the gamelet was prepared successfully, false otherwise.</returns> public bool TrySelectAndPrepareGamelet(string targetPath, DeployOnLaunchSetting deployOnLaunchSetting, List <Gamelet> gamelets, TestAccount testAccount, string devAccount, out Gamelet gamelet) { if (!TrySelectGamelet(gamelets, out gamelet)) { return(false); } if (!StopGameLaunchIfPresent(testAccount, devAccount, gamelets, gamelet)) { return(false); } /// If developer runs a game with a test account first, then switches the account and /// tries to run a game on the same gamelet, the <see cref="StopGameLaunchIfPresent"/> /// won't stop the running game on the gamelet. if (!StopGameIfNeeded(ref gamelet)) { return(false); } if (!EnableSsh(gamelet)) { return(false); } if (!ValidateMountConfiguration(targetPath, deployOnLaunchSetting, gamelet)) { return(false); } if (!ClearLogs(gamelet)) { return(false); } return(true); }
/// <summary> /// Selects a gamelet from the given list and prepares it for running a game. /// </summary> /// <exception cref="InvalidStateException"> /// Thrown when the selected gamelet is in an unexpected state.</exception> /// <exception cref="ConfigurationException"> /// Thrown if there is no gamelet reserved</exception> /// <exception cref="CloudException">Thrown if there are any RPC errors.</exception> /// <returns>True if the gamelet was prepared successfully, false otherwise.</returns> public bool TrySelectAndPrepareGamelet(string targetPath, DeployOnLaunchSetting deployOnLaunchSetting, List <Gamelet> gamelets, TestAccount testAccount, string devAccount, out Gamelet result) { Gamelet gamelet = result = null; if (!TrySelectGamelet(gamelets, out gamelet)) { return(false); } if (gamelet.State == GameletState.InUse) { if (!PromptStopGamelet(ref gamelet)) { return(false); } } if (!EnableSsh(gamelet)) { return(false); } if (!ValidateMountConfiguration(targetPath, deployOnLaunchSetting, gamelet)) { return(false); } if (!ClearLogs(gamelet)) { return(false); } result = gamelet; return(true); }
public async Task DeployGameExecutableDeploysBinaryAndSetsExecutableBitAsync( [Values(DeployOnLaunchSetting.DELTA, DeployOnLaunchSetting.ALWAYS)] DeployOnLaunchSetting value) { string localPath = GetLocalPath(); IAsyncProject project = GetProjectWithLocalPathAndDeployMode(localPath, value); (SshTarget target, IAction action, ICancelable cancelable) = GetDeploymentArguments(); (IRemoteFile file, IRemoteCommand command, IRemoteDeploy deploy, _) = GetTestObjects(); await deploy.DeployGameExecutableAsync(project, target, cancelable, action); Assert.Multiple(async() => { await file.Received(1).SyncAsync(target, localPath, YetiConstants.RemoteDeployPath, cancelable, Arg.Any <bool>()); await command.Received(1) .RunWithSuccessAsync( target, $"chmod a+x {YetiConstants.RemoteDeployPath}{_binaryName}"); }); }
public async Task DeployGameExecutableAsync(IAsyncProject project, SshTarget target, ICancelable task, Metrics.IAction action) { DataRecorder record = new DataRecorder(action, DataRecorder.File.GAME_EXECUTABLE); DeployOnLaunchSetting deploySetting = await GetDeployOnLaunchSettingAsync(project); if (deploySetting != DeployOnLaunchSetting.FALSE) { string localPath = await project.GetTargetPathAsync(); bool force = (deploySetting == DeployOnLaunchSetting.ALWAYS); await DeployToTargetAsync(record, task, target, localPath, YetiConstants.RemoteDeployPath, force); string targetName = Path.GetFileName(localPath); string remotePath = Path.Combine(YetiConstants.RemoteDeployPath, targetName); await SetRemoteExecutableBitAsync(target, remotePath, record); } else { record.SetCopyAttempted(false); record.SignatureCheckResult(BinarySignatureCheck.Types.Result.NoCopy); } }
public void SetDeployOnLaunch(DeployOnLaunchSetting deployOnLaunch) => SetStringProperty(ProjectPropertyName.GgpDeployOnLaunch, deployOnLaunch.ToString());
/// <summary> /// Check whether the deployment configuration of the binary works correctly /// with the mount configuration of the gamelet. /// </summary> /// <param name="targetPath">Path to the generated binary.</param> /// <param name="deployOnLaunchSetting">Project's "Deploy On Launch" value.</param> /// <param name="gamelet">Gamelet to connect to.</param> /// <returns>True if no issues found or the user decided to proceed.</returns> bool ValidateMountConfiguration(string targetPath, DeployOnLaunchSetting deployOnLaunchSetting, Gamelet gamelet) { MountConfiguration configuration = _mountChecker.GetConfiguration(gamelet, _actionRecorder); string targetPathNormalized = GetNormalizedFullPath(targetPath); Trace.WriteLine($"TargetPath is set to {targetPathNormalized}"); // If the /srv/game/assets folder is detached from /mnt/developer then // binaries generated by VS won't be used during the run/debug process. // Notify the user and let them decide whether this is expected behaviour or not. if (_mountChecker.IsGameAssetsDetachedFromDeveloperFolder(configuration)) { // 'Yes' - continue; 'No' - interrupt (gamelet validation fails). return(_dialogUtil.ShowYesNo( ErrorStrings.MountConfigurationWarning(YetiConstants.GameAssetsMountingPoint, YetiConstants.DeveloperMountingPoint), _mountConfigurationDialogCaption)); } if (_mountChecker.IsAssetStreamingActivated(configuration)) { var sshChannels = new SshTunnels(); IEnumerable <string> commandLines = sshChannels.GetSshCommandLines(); string[] mountPoints = sshChannels.ExtractMountingPoints(commandLines).ToArray(); if (mountPoints.Length == 0) { // If asset streaming is set up on the gamelet but there is no ssh tunnels // between the workstation and the gamelet then the connection was // probably lost (or asset streaming is set to a different machine, and // then it's ok). // 'Yes' - continue; 'No' - interrupt (gamelet validation fails). return(_dialogUtil.ShowYesNo(ErrorStrings.AssetStreamingBrokenWarning(), _mountConfigurationDialogCaption)); } if (deployOnLaunchSetting != DeployOnLaunchSetting.FALSE) { foreach (string mountPoint in mountPoints) { string mountPointNormalized = GetNormalizedFullPath(mountPoint); if (targetPathNormalized.StartsWith($@"{mountPointNormalized}\")) { // The mount point folder matches the output folder for the binaries; // VS will try to upload the binaries to the gamelet and this might lead // to an exception during 'scp' call. Instead, asset streaming should // take care of uploading the generated data to the gamelet. 'Yes' - // continue; 'No' - interrupt (gamelet validation fails). string current = GgpDeployOnLaunchToDisplayName(deployOnLaunchSetting); string expected = GgpDeployOnLaunchToDisplayName(DeployOnLaunchSetting.FALSE); return(_dialogUtil.ShowYesNo( ErrorStrings.AssetStreamingDeployWarning(mountPointNormalized, current, expected), _mountConfigurationDialogCaption)); } } } } return(true); string GetNormalizedFullPath(string path) { if (string.IsNullOrWhiteSpace(path)) { return(path); } string normalizedPath = FileUtil.GetNormalizedPath(path); if (File.Exists(normalizedPath) && FileUtil.IsPathSymlink(normalizedPath)) { string symlinkTarget = NativeMethods.GetTargetPathName(path); return(FileUtil.GetNormalizedPath(symlinkTarget)); } return(normalizedPath); } }
public async Task <IReadOnlyList <IDebugLaunchSettings> > QueryDebugTargetsAsync( IAsyncProject project, DebugLaunchOptions launchOptions) { try { // Make sure we can find the target executable. var targetPath = await project.GetTargetPathAsync(); if (!_fileSystem.File.Exists(targetPath)) { Trace.WriteLine($"Unable to find target executable: {targetPath}"); _dialogUtil.ShowError(ErrorStrings.UnableToFindTargetExecutable(targetPath)); return(new IDebugLaunchSettings[] { }); } _metrics.UseNewDebugSessionId(); var actionRecorder = new ActionRecorder(_metrics); var targetFileName = await project.GetTargetFileNameAsync(); var gameletCommand = (targetFileName + " " + await project.GetGameletLaunchArgumentsAsync()).Trim(); var launchParams = new LaunchParams() { Cmd = gameletCommand, RenderDoc = await project.GetLaunchRenderDocAsync(), Rgp = await project.GetLaunchRgpAsync(), SurfaceEnforcementMode = await project.GetSurfaceEnforcementAsync(), VulkanDriverVariant = await project.GetVulkanDriverVariantAsync(), QueryParams = await project.GetQueryParamsAsync(), Endpoint = await project.GetEndpointAsync() }; if (_sdkVersion != null && !string.IsNullOrEmpty(_sdkVersion.ToString())) { launchParams.SdkVersion = _sdkVersion.ToString(); } if (!TrySetupQueries(project, actionRecorder, out SetupQueriesResult setupQueriesResult)) { return(new IDebugLaunchSettings[] { }); } launchParams.ApplicationName = setupQueriesResult.Application.Name; launchParams.ApplicationId = setupQueriesResult.Application.Id; if (setupQueriesResult.TestAccount != null) { launchParams.TestAccount = setupQueriesResult.TestAccount.Name; launchParams.TestAccountGamerName = setupQueriesResult.TestAccount.GamerStadiaName; } DeployOnLaunchSetting deployOnLaunchAsync = await project.GetDeployOnLaunchAsync(); launchParams.Account = _credentialManager.LoadAccount(); // TODO: Enable PlayerEndpoint Launches for non-internal usage in VS. if (launchParams.Endpoint == StadiaEndpoint.PlayerEndpoint && launchParams.Account != null && !launchParams.Account.EndsWith("@sparklingsunset.com") && !launchParams.Account.EndsWith("@subtlesunset.com")) { throw new NotImplementedException( "Player Endpoints are not yet supported, please select " + "Test Client in the Project Properties instead."); } // TODO: Enable launch on any endpoint for external accounts. if (launchParams.Endpoint == StadiaEndpoint.AnyEndpoint && launchParams.Account != null && !launchParams.Account.EndsWith("@sparklingsunset.com") && !launchParams.Account.EndsWith("@subtlesunset.com")) { throw new NotImplementedException( "Launch on any player endpoint is not supported yet, please select " + "another endpoint in the Project Properties instead."); } bool launchGameApiEnabled = _yetiVsiService.Options.LaunchGameApiFlow == LaunchGameApiFlow.ENABLED; IGameletSelector gameletSelector = _gameletSelectorFactory.Create(launchGameApiEnabled, actionRecorder); if (!gameletSelector.TrySelectAndPrepareGamelet( targetPath, deployOnLaunchAsync, setupQueriesResult.Gamelets, setupQueriesResult.TestAccount, launchParams.Account, out Gamelet gamelet)) { return(new IDebugLaunchSettings[] { }); } launchParams.GameletName = gamelet.Name; launchParams.PoolId = gamelet.PoolId; launchParams.GameletSdkVersion = gamelet.GameletVersions.DevToolingVersion; launchParams.GameletEnvironmentVars = await project.GetGameletEnvironmentVariablesAsync(); // Prepare for debug launch using these settings. var debugLaunchSettings = new DebugLaunchSettings(launchOptions); debugLaunchSettings.Environment["PATH"] = await project.GetExecutablePathAsync(); debugLaunchSettings.LaunchOperation = DebugLaunchOperation.CreateProcess; debugLaunchSettings.CurrentDirectory = await project.GetAbsoluteRootPathAsync(); if (!launchOptions.HasFlag(DebugLaunchOptions.NoDebug)) { var parameters = _paramsFactory.Create(); parameters.TargetIp = new SshTarget(gamelet).GetString(); parameters.DebugSessionId = _metrics.DebugSessionId; debugLaunchSettings.Options = _paramsFactory.Serialize(parameters); } IAction action = actionRecorder.CreateToolAction(ActionType.RemoteDeploy); bool isDeployed = _cancelableTaskFactory.Create( TaskMessages.DeployingExecutable, async task => { await _remoteDeploy.DeployGameExecutableAsync( project, new SshTarget(gamelet), task, action); task.Progress.Report(TaskMessages.CustomDeployCommand); await _remoteDeploy.ExecuteCustomCommandAsync(project, gamelet, action); }).RunAndRecord(action); if (!isDeployed) { return(new IDebugLaunchSettings[] { }); } if (launchOptions.HasFlag(DebugLaunchOptions.NoDebug)) { if (_gameLauncher.LaunchGameApiEnabled || launchParams.Endpoint == StadiaEndpoint.PlayerEndpoint || launchParams.Endpoint == StadiaEndpoint.AnyEndpoint) { IVsiGameLaunch launch = _gameLauncher.CreateLaunch(launchParams); if (launch != null) { debugLaunchSettings.Arguments = _launchCommandFormatter.CreateWithLaunchName( launchParams, launch.LaunchName); } else { Trace.WriteLine("Unable to retrieve launch name from the launch api."); return(new IDebugLaunchSettings[] { }); } } else { debugLaunchSettings.Arguments = _launchCommandFormatter.CreateFromParams(launchParams); } debugLaunchSettings.Executable = Path.Combine(Environment.SystemDirectory, YetiConstants.Command); debugLaunchSettings.LaunchOptions = DebugLaunchOptions.NoDebug | DebugLaunchOptions.MergeEnvironment; } else { if (_yetiVsiService.DebuggerOptions[DebuggerOption.SKIP_WAIT_LAUNCH] == DebuggerOptionState.DISABLED) { launchParams.Debug = true; } // TODO: This should really be the game_client executable, since // the args we pass are for game_client as well. We just need to find another // way to pass the game executable. debugLaunchSettings.Executable = targetPath; debugLaunchSettings.LaunchDebugEngineGuid = YetiConstants.DebugEngineGuid; debugLaunchSettings.Arguments = _launchCommandFormatter.EncodeLaunchParams(launchParams); debugLaunchSettings.LaunchOptions = DebugLaunchOptions.MergeEnvironment; } return(new IDebugLaunchSettings[] { debugLaunchSettings }); } catch (Exception e) { Trace.WriteLine(e.ToString()); _dialogUtil.ShowError(e.Message, e.ToString()); return(new IDebugLaunchSettings[] { }); } }