Skip to content

Commit 0000e6e

Browse files
authored
Merge pull request microsoft#1916 from tyrielv/tyrielv/dev-func-tests
functional tests: add dev mode for running without admin
2 parents 820341d + 4e30816 commit 0000e6e

9 files changed

Lines changed: 354 additions & 22 deletions

File tree

GVFS/GVFS.FunctionalTests/GVFSTestConfig.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public static class GVFSTestConfig
1616

1717
public static bool ReplaceInboxProjFS { get; set; }
1818

19+
public static bool IsDevMode { get; set; }
20+
1921
public static string PathToGVFS
2022
{
2123
get

GVFS/GVFS.FunctionalTests/Program.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using GVFS.Common;
12
using GVFS.FunctionalTests.Properties;
23
using GVFS.FunctionalTests.Tools;
34
using GVFS.PlatformLoader;
@@ -15,10 +16,17 @@ public static void Main(string[] args)
1516
{
1617
Properties.Settings.Default.Initialize();
1718
GVFSPlatformLoader.Initialize();
19+
20+
GVFSTestConfig.IsDevMode = Environment.GetEnvironmentVariable("GVFS_FUNCTIONAL_TEST_DEV_MODE") == "1";
21+
1822
Console.WriteLine("Settings.Default.CurrentDirectory: {0}", Settings.Default.CurrentDirectory);
1923
Console.WriteLine("Settings.Default.PathToGit: {0}", Settings.Default.PathToGit);
2024
Console.WriteLine("Settings.Default.PathToGVFS: {0}", Settings.Default.PathToGVFS);
2125
Console.WriteLine("Settings.Default.PathToGVFSService: {0}", Settings.Default.PathToGVFSService);
26+
if (GVFSTestConfig.IsDevMode)
27+
{
28+
Console.WriteLine("*** Dev mode enabled (GVFS_FUNCTIONAL_TEST_DEV_MODE=1) ***");
29+
}
2230

2331
NUnitRunner runner = new NUnitRunner(args);
2432
runner.AddGlobalSetupIfNeeded("GVFS.FunctionalTests.GlobalSetup");
@@ -140,11 +148,8 @@ private static void RunBeforeAnyTests()
140148

141149
GVFSServiceProcess.InstallService();
142150

143-
string serviceProgramDataDir = Path.Combine(
144-
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles, Environment.SpecialFolderOption.Create),
145-
"GVFS",
146-
"ProgramData",
147-
"GVFS.Service");
151+
string serviceProgramDataDir = GVFSPlatform.Instance.GetSecureDataRootForGVFSComponent(
152+
GVFSConstants.Service.ServiceName);
148153

149154
string statusCacheVersionTokenPath = Path.Combine(
150155
serviceProgramDataDir, "EnableGitStatusCacheToken.dat");

GVFS/GVFS.FunctionalTests/Settings.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,30 @@ public static void Initialize()
4545
Commitish = @"FunctionalTests/20201014";
4646

4747
EnlistmentRoot = @"C:\Repos\GVFSFunctionalTests\enlistment";
48-
PathToGVFS = @"C:\Program Files\VFS for Git\GVFS.exe";
49-
PathToGit = @"C:\Program Files\Git\cmd\git.exe";
50-
PathToBash = @"C:\Program Files\Git\bin\bash.exe";
5148

5249
ControlGitRepoRoot = @"C:\Repos\GVFSFunctionalTests\ControlRepo";
5350
FastFetchBaseRoot = @"C:\Repos\GVFSFunctionalTests\FastFetch";
5451
FastFetchRoot = Path.Combine(FastFetchBaseRoot, "test");
5552
FastFetchControl = Path.Combine(FastFetchBaseRoot, "control");
56-
PathToGVFSService = @"C:\Program Files\VFS for Git\GVFS.Service.exe";
5753
BinaryFileNameExtension = ".exe";
54+
55+
string devModeOutDir = Environment.GetEnvironmentVariable("GVFS_DEV_OUT_DIR");
56+
if (!string.IsNullOrEmpty(devModeOutDir))
57+
{
58+
string configuration = Environment.GetEnvironmentVariable("GVFS_DEV_CONFIGURATION") ?? "Debug";
59+
string payloadDir = Path.Combine(devModeOutDir, "GVFS.Payload", "bin", configuration, "win-x64");
60+
61+
PathToGVFS = Path.Combine(payloadDir, "gvfs.exe");
62+
PathToGVFSService = Path.Combine(payloadDir, "GVFS.Service.exe");
63+
}
64+
else
65+
{
66+
PathToGVFS = @"C:\Program Files\VFS for Git\GVFS.exe";
67+
PathToGVFSService = @"C:\Program Files\VFS for Git\GVFS.Service.exe";
68+
}
69+
70+
PathToGit = @"C:\Program Files\Git\cmd\git.exe";
71+
PathToBash = @"C:\Program Files\Git\bin\bash.exe";
5872
}
5973
}
6074
}

GVFS/GVFS.FunctionalTests/Tools/GVFSServiceProcess.cs

Lines changed: 142 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace GVFS.FunctionalTests.Tools
1212
public static class GVFSServiceProcess
1313
{
1414
private static readonly string ServiceNameArgument = "--servicename=" + TestServiceName;
15+
private static Process consoleServiceProcess;
1516

1617
public static string TestServiceName
1718
{
@@ -22,10 +23,145 @@ public static string TestServiceName
2223
}
2324

2425
public static void InstallService()
26+
{
27+
if (GVFSTestConfig.IsDevMode)
28+
{
29+
StartServiceAsConsoleProcess();
30+
}
31+
else
32+
{
33+
InstallWindowsService();
34+
}
35+
}
36+
37+
public static void UninstallService()
38+
{
39+
if (GVFSTestConfig.IsDevMode)
40+
{
41+
StopConsoleServiceProcess();
42+
CleanupServiceData();
43+
}
44+
else
45+
{
46+
UninstallWindowsService();
47+
}
48+
}
49+
50+
public static void StartService()
51+
{
52+
if (GVFSTestConfig.IsDevMode)
53+
{
54+
StartServiceAsConsoleProcess();
55+
}
56+
else
57+
{
58+
StartWindowsService();
59+
}
60+
}
61+
62+
public static void StopService()
63+
{
64+
if (GVFSTestConfig.IsDevMode)
65+
{
66+
StopConsoleServiceProcess();
67+
}
68+
else
69+
{
70+
StopWindowsService();
71+
}
72+
}
73+
74+
private static void StartServiceAsConsoleProcess()
75+
{
76+
StopConsoleServiceProcess();
77+
78+
string pathToService = GetPathToService();
79+
Console.WriteLine("Starting test service in console mode: " + pathToService);
80+
81+
ProcessStartInfo startInfo = new ProcessStartInfo(pathToService);
82+
startInfo.Arguments = $"--console {ServiceNameArgument}";
83+
startInfo.UseShellExecute = false;
84+
startInfo.CreateNoWindow = true;
85+
startInfo.RedirectStandardOutput = true;
86+
startInfo.RedirectStandardError = true;
87+
88+
consoleServiceProcess = Process.Start(startInfo);
89+
consoleServiceProcess.ShouldNotBeNull("Failed to start test service process");
90+
91+
// Consume output asynchronously to prevent buffer deadlock
92+
consoleServiceProcess.BeginOutputReadLine();
93+
consoleServiceProcess.BeginErrorReadLine();
94+
95+
// Wait for the service to start listening on its named pipe
96+
string pipeName = TestServiceName + ".pipe";
97+
int retries = 50;
98+
while (retries-- > 0)
99+
{
100+
if (consoleServiceProcess.HasExited)
101+
{
102+
throw new InvalidOperationException(
103+
$"Test service process exited with code {consoleServiceProcess.ExitCode} before becoming ready");
104+
}
105+
106+
if (File.Exists(@"\\.\pipe\" + pipeName))
107+
{
108+
Console.WriteLine("Test service is ready (pipe: " + pipeName + ")");
109+
return;
110+
}
111+
112+
Thread.Sleep(200);
113+
}
114+
115+
throw new System.TimeoutException("Timed out waiting for test service pipe: " + pipeName);
116+
}
117+
118+
private static void StopConsoleServiceProcess()
119+
{
120+
if (consoleServiceProcess != null && !consoleServiceProcess.HasExited)
121+
{
122+
try
123+
{
124+
Console.WriteLine("Stopping test service console process (PID: " + consoleServiceProcess.Id + ")");
125+
consoleServiceProcess.Kill();
126+
consoleServiceProcess.WaitForExit(5000);
127+
}
128+
catch (InvalidOperationException)
129+
{
130+
// Process already exited
131+
}
132+
133+
consoleServiceProcess = null;
134+
}
135+
}
136+
137+
private static void CleanupServiceData()
138+
{
139+
string commonAppDataRoot = Environment.GetEnvironmentVariable("GVFS_COMMON_APPDATA_ROOT");
140+
string serviceData;
141+
if (!string.IsNullOrEmpty(commonAppDataRoot))
142+
{
143+
serviceData = Path.Combine(commonAppDataRoot, TestServiceName);
144+
}
145+
else
146+
{
147+
serviceData = Path.Combine(
148+
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
149+
"GVFS",
150+
TestServiceName);
151+
}
152+
153+
DirectoryInfo serviceDataDir = new DirectoryInfo(serviceData);
154+
if (serviceDataDir.Exists)
155+
{
156+
serviceDataDir.Delete(true);
157+
}
158+
}
159+
160+
private static void InstallWindowsService()
25161
{
26162
Console.WriteLine("Installing " + TestServiceName);
27163

28-
UninstallService();
164+
UninstallWindowsService();
29165

30166
// Wait for delete to complete. If the services control panel is open, this will never complete.
31167
while (RunScCommand("query", TestServiceName).ExitCode == 0)
@@ -47,12 +183,12 @@ public static void InstallService()
47183
ProcessResult result = RunScCommand("create", createServiceArguments);
48184
result.ExitCode.ShouldEqual(0, "Failure while running sc create " + createServiceArguments + "\r\n" + result.Output);
49185

50-
StartService();
186+
StartWindowsService();
51187
}
52188

53-
public static void UninstallService()
189+
private static void UninstallWindowsService()
54190
{
55-
StopService();
191+
StopWindowsService();
56192

57193
RunScCommand("delete", TestServiceName);
58194

@@ -65,7 +201,7 @@ public static void UninstallService()
65201
}
66202
}
67203

68-
public static void StartService()
204+
private static void StartWindowsService()
69205
{
70206
ServiceController testService = ServiceController.GetServices().SingleOrDefault(service => service.ServiceName == TestServiceName);
71207
testService.ShouldNotBeNull($"{TestServiceName} does not exist as a service");
@@ -78,7 +214,7 @@ public static void StartService()
78214
}
79215
}
80216

81-
public static void StopService()
217+
private static void StopWindowsService()
82218
{
83219
try
84220
{

GVFS/GVFS.Platform.Windows/ProjFSFilter.cs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class ProjFSFilter : IKernelDriver
3838

3939
private const uint OkResult = 0;
4040
private const uint NameCollisionErrorResult = 0x801F0012;
41+
private const uint AccessDeniedResult = 0x80070005;
4142

4243
private enum ProjFSInboxStatus
4344
{
@@ -460,10 +461,35 @@ public bool TryPrepareFolderForCallbacks(string folderPath, out string error, ou
460461
public bool IsReady(JsonTracer tracer, string enlistmentRoot, TextWriter output, out string error)
461462
{
462463
error = string.Empty;
463-
return
464-
IsServiceRunning(tracer) &&
465-
IsNativeLibInstalled(tracer, new PhysicalFileSystem()) &&
466-
TryAttach(enlistmentRoot, out error);
464+
if (!IsServiceRunning(tracer))
465+
{
466+
error = "ProjFS (prjflt) service is not running";
467+
return false;
468+
}
469+
470+
if (!IsNativeLibInstalled(tracer, new PhysicalFileSystem()))
471+
{
472+
error = "ProjFS native library is not installed";
473+
return false;
474+
}
475+
476+
if (!TryAttach(enlistmentRoot, out error))
477+
{
478+
// FilterAttach requires SE_LOAD_DRIVER_PRIVILEGE (admin). When running
479+
// non-elevated on a machine where ProjFS is already set up, the filter
480+
// is already attached to the volume and the only failure is ACCESS_DENIED.
481+
// Allow the mount to proceed in that specific case.
482+
if (error.Contains(AccessDeniedResult.ToString()))
483+
{
484+
tracer.RelatedInfo($"IsReady: TryAttach returned ACCESS_DENIED, but ProjFS service is running. Proceeding.");
485+
error = string.Empty;
486+
return true;
487+
}
488+
489+
return false;
490+
}
491+
492+
return true;
467493
}
468494

469495
public bool RegisterForOfflineIO()

GVFS/GVFS.Platform.Windows/WindowsPlatform.Shared.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ public static string GetNamedPipeNameImplementation(string enlistmentRoot)
7777

7878
public static string GetSecureDataRootForGVFSImplementation()
7979
{
80+
string envOverride = Environment.GetEnvironmentVariable("GVFS_SECURE_DATA_ROOT");
81+
if (!string.IsNullOrEmpty(envOverride))
82+
{
83+
return envOverride;
84+
}
85+
8086
return Path.Combine(
8187
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles, Environment.SpecialFolderOption.Create),
8288
"GVFS",
@@ -85,6 +91,12 @@ public static string GetSecureDataRootForGVFSImplementation()
8591

8692
public static string GetCommonAppDataRootForGVFSImplementation()
8793
{
94+
string envOverride = Environment.GetEnvironmentVariable("GVFS_COMMON_APPDATA_ROOT");
95+
if (!string.IsNullOrEmpty(envOverride))
96+
{
97+
return envOverride;
98+
}
99+
88100
return Path.Combine(
89101
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData, Environment.SpecialFolderOption.Create),
90102
"GVFS");

GVFS/GVFS.Service/GVFSService.Windows.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,53 @@ protected override void OnSessionChange(SessionChangeDescription changeDescripti
152152
}
153153
}
154154

155+
public void RunInConsoleMode(string[] args)
156+
{
157+
if (this.serviceThread != null)
158+
{
159+
throw new InvalidOperationException("Cannot start service twice in a row.");
160+
}
161+
162+
string serviceName = args.FirstOrDefault(arg => arg.StartsWith(ServiceNameArgPrefix));
163+
if (serviceName != null)
164+
{
165+
this.serviceName = serviceName.Substring(ServiceNameArgPrefix.Length);
166+
}
167+
168+
string serviceLogsDirectoryPath = GVFSPlatform.Instance.GetLogsDirectoryForGVFSComponent(this.serviceName);
169+
170+
Directory.CreateDirectory(serviceLogsDirectoryPath);
171+
this.tracer.AddLogFileEventListener(
172+
GVFSEnlistment.GetNewGVFSLogFileName(serviceLogsDirectoryPath, GVFSConstants.LogFileTypes.Service),
173+
EventLevel.Verbose,
174+
Keywords.Any);
175+
176+
try
177+
{
178+
this.serviceDataLocation = GVFSPlatform.Instance.GetSecureDataRootForGVFSComponent(this.serviceName);
179+
Directory.CreateDirectory(this.serviceDataLocation);
180+
Directory.CreateDirectory(Path.GetDirectoryName(this.serviceDataLocation));
181+
182+
this.serviceStopped = new ManualResetEvent(false);
183+
184+
Console.WriteLine($"GVFS.Service running in console mode as '{this.serviceName}'");
185+
Console.WriteLine("Press Ctrl+C to stop.");
186+
187+
Console.CancelKeyPress += (sender, e) =>
188+
{
189+
e.Cancel = true;
190+
this.StopRunning();
191+
};
192+
193+
this.Run();
194+
}
195+
catch (Exception e)
196+
{
197+
this.tracer.RelatedError($"Console mode failed: {e}");
198+
throw;
199+
}
200+
}
201+
155202
protected override void OnStart(string[] args)
156203
{
157204
if (this.serviceThread != null)

0 commit comments

Comments
 (0)