add skip back for windows

This commit is contained in:
Salman Chishti
2025-07-28 13:01:04 +00:00
parent 577c73ee80
commit 49b30c8a23

View File

@@ -11,7 +11,6 @@ namespace GitHub.Runner.Common.Tests.Listener
{ {
public sealed class ShellScriptSyntaxL0 public sealed class ShellScriptSyntaxL0
{ {
// Generic method to test any shell script template for bash syntax errors
private void ValidateShellScriptTemplateSyntax(string relativePath, string templateName, bool shouldPass = true, Func<string, string> templateModifier = null, bool useFullPath = false) private void ValidateShellScriptTemplateSyntax(string relativePath, string templateName, bool shouldPass = true, Func<string, string> templateModifier = null, bool useFullPath = false)
{ {
// Skip on Windows // Skip on Windows
@@ -46,28 +45,15 @@ namespace GitHub.Runner.Common.Tests.Listener
// Read the template // Read the template
string template = File.ReadAllText(templatePath); string template = File.ReadAllText(templatePath);
// Log debug info
File.WriteAllText(debugLogPath, $"Template file: {templatePath}\n");
File.AppendAllText(debugLogPath, $"Template exists: {File.Exists(templatePath)}\n");
File.AppendAllText(debugLogPath, $"Template size: {template.Length} bytes\n");
// Apply template modifier if provided (for injecting errors) // Apply template modifier if provided (for injecting errors)
if (templateModifier != null) if (templateModifier != null)
{ {
template = templateModifier(template); template = templateModifier(template);
File.AppendAllText(debugLogPath, $"Template was modified by templateModifier\n");
} }
// Replace common placeholders with valid test values // Replace common placeholders with valid test values
string rootFolder = useFullPath ? Path.GetDirectoryName(templatePath) : Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), "..")); string rootFolder = useFullPath ? Path.GetDirectoryName(templatePath) : Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), ".."));
template = ReplaceCommonPlaceholders(template, rootFolder, tempDir); template = ReplaceCommonPlaceholders(template, rootFolder, tempDir);
File.AppendAllText(debugLogPath, $"Template placeholders replaced\n");
File.AppendAllText(debugLogPath, $"Processed template size: {template.Length} bytes\n");
// Save a copy of the processed template for debugging
string debugTemplatePath = Path.Combine(tempDir, $"debug_{Path.GetFileNameWithoutExtension(templateName)}.sh");
File.WriteAllText(debugTemplatePath, template);
File.AppendAllText(debugLogPath, $"Debug template saved to: {debugTemplatePath}\n");
// Write the processed template to a temporary file // Write the processed template to a temporary file
File.WriteAllText(tempScriptPath, template); File.WriteAllText(tempScriptPath, template);
@@ -87,16 +73,11 @@ namespace GitHub.Runner.Common.Tests.Listener
bashCheckProcess.StartInfo.RedirectStandardError = true; bashCheckProcess.StartInfo.RedirectStandardError = true;
bashCheckProcess.StartInfo.UseShellExecute = false; bashCheckProcess.StartInfo.UseShellExecute = false;
File.AppendAllText(debugLogPath, $"Running bash check: bash -n {tempScriptPath}\n");
bashCheckProcess.Start(); bashCheckProcess.Start();
string bashCheckOutput = bashCheckProcess.StandardOutput.ReadToEnd(); string bashCheckOutput = bashCheckProcess.StandardOutput.ReadToEnd();
string bashCheckErrors = bashCheckProcess.StandardError.ReadToEnd(); string bashCheckErrors = bashCheckProcess.StandardError.ReadToEnd();
bashCheckProcess.WaitForExit(); bashCheckProcess.WaitForExit();
File.AppendAllText(debugLogPath, $"Bash check exit code: {bashCheckProcess.ExitCode}\n");
File.AppendAllText(debugLogPath, $"Bash check output: {bashCheckOutput}\n");
File.AppendAllText(debugLogPath, $"Bash check errors: {bashCheckErrors}\n");
// Act - Check syntax using bash -n // Act - Check syntax using bash -n
var process = new Process(); var process = new Process();
process.StartInfo.FileName = "bash"; process.StartInfo.FileName = "bash";
@@ -104,24 +85,15 @@ namespace GitHub.Runner.Common.Tests.Listener
process.StartInfo.RedirectStandardError = true; process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = false; process.StartInfo.UseShellExecute = false;
Console.WriteLine($"Executing: bash -n {tempScriptPath}");
File.AppendAllText(debugLogPath, $"Executing main test: bash -n {tempScriptPath}\n");
process.Start(); process.Start();
string errors = process.StandardError.ReadToEnd(); string errors = process.StandardError.ReadToEnd();
process.WaitForExit(); process.WaitForExit();
Console.WriteLine($"Process exited with code: {process.ExitCode}");
File.AppendAllText(debugLogPath, $"Process exit code: {process.ExitCode}\n");
if (!string.IsNullOrEmpty(errors)) if (!string.IsNullOrEmpty(errors))
{ {
Console.WriteLine($"Errors: {errors}"); Console.WriteLine($"Errors: {errors}");
File.AppendAllText(debugLogPath, $"Errors: {errors}\n");
} }
// For debugging only
Console.WriteLine($"Debug log saved at: {debugLogPath}");
// Assert based on expected outcome // Assert based on expected outcome
if (shouldPass) if (shouldPass)
{ {
@@ -160,10 +132,8 @@ namespace GitHub.Runner.Common.Tests.Listener
} }
} }
// Helper method to replace common placeholders in shell script templates
private string ReplaceCommonPlaceholders(string template, string rootDirectory, string tempDir) private string ReplaceCommonPlaceholders(string template, string rootDirectory, string tempDir)
{ {
// Replace common placeholders
template = template.Replace("_PROCESS_ID_", "1234"); template = template.Replace("_PROCESS_ID_", "1234");
template = template.Replace("_RUNNER_PROCESS_NAME_", "Runner.Listener"); template = template.Replace("_RUNNER_PROCESS_NAME_", "Runner.Listener");
template = template.Replace("_ROOT_FOLDER_", rootDirectory); template = template.Replace("_ROOT_FOLDER_", rootDirectory);
@@ -186,82 +156,74 @@ namespace GitHub.Runner.Common.Tests.Listener
[Trait("SkipOn", "windows")] [Trait("SkipOn", "windows")]
public void UpdateShTemplateHasValidSyntax() public void UpdateShTemplateHasValidSyntax()
{ {
// Add debugging info if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
Console.WriteLine($"Running on platform: {RuntimeInformation.OSDescription}, Architecture: {RuntimeInformation.OSArchitecture}"); {
return;
}
try try
{ {
using (var hc = new TestHostContext(this)) using (var hc = new TestHostContext(this))
{ {
// First validate with bash -n
ValidateShellScriptTemplateSyntax("src/Misc/layoutbin", "update.sh.template"); ValidateShellScriptTemplateSyntax("src/Misc/layoutbin", "update.sh.template");
// Additional validation with ShellCheck if available
string rootDirectory = Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), "..")); string rootDirectory = Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), ".."));
string templatePath = Path.Combine(rootDirectory, "src/Misc/layoutbin", "update.sh.template"); string templatePath = Path.Combine(rootDirectory, "src/Misc/layoutbin", "update.sh.template");
string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempDir); Directory.CreateDirectory(tempDir);
string tempScriptPath = Path.Combine(tempDir, Path.GetFileNameWithoutExtension("update.sh.template")); string tempScriptPath = Path.Combine(tempDir, Path.GetFileNameWithoutExtension("update.sh.template"));
// Read the template // Read the template
string template = File.ReadAllText(templatePath); string template = File.ReadAllText(templatePath);
// Replace placeholders with valid test values // Replace placeholders with valid test values
template = ReplaceCommonPlaceholders(template, rootDirectory, tempDir); template = ReplaceCommonPlaceholders(template, rootDirectory, tempDir);
// Write the processed template to a temporary file // Write the processed template to a temporary file
File.WriteAllText(tempScriptPath, template); File.WriteAllText(tempScriptPath, template);
// Make the file executable // Make the file executable
var chmodProcess = new Process(); var chmodProcess = new Process();
chmodProcess.StartInfo.FileName = "chmod"; chmodProcess.StartInfo.FileName = "chmod";
chmodProcess.StartInfo.Arguments = $"+x {tempScriptPath}"; chmodProcess.StartInfo.Arguments = $"+x {tempScriptPath}";
chmodProcess.Start(); chmodProcess.Start();
chmodProcess.WaitForExit(); chmodProcess.WaitForExit();
// Check if ShellCheck is available // Check if ShellCheck is available
var shellcheckExistsProcess = new Process(); var shellcheckExistsProcess = new Process();
shellcheckExistsProcess.StartInfo.FileName = "which"; shellcheckExistsProcess.StartInfo.FileName = "which";
shellcheckExistsProcess.StartInfo.Arguments = "shellcheck"; shellcheckExistsProcess.StartInfo.Arguments = "shellcheck";
shellcheckExistsProcess.StartInfo.RedirectStandardOutput = true; shellcheckExistsProcess.StartInfo.RedirectStandardOutput = true;
shellcheckExistsProcess.StartInfo.UseShellExecute = false; shellcheckExistsProcess.StartInfo.UseShellExecute = false;
shellcheckExistsProcess.Start(); shellcheckExistsProcess.Start();
string shellcheckPath = shellcheckExistsProcess.StandardOutput.ReadToEnd().Trim(); string shellcheckPath = shellcheckExistsProcess.StandardOutput.ReadToEnd().Trim();
shellcheckExistsProcess.WaitForExit(); shellcheckExistsProcess.WaitForExit();
if (!string.IsNullOrEmpty(shellcheckPath)) if (!string.IsNullOrEmpty(shellcheckPath))
{ {
Console.WriteLine("ShellCheck found, performing additional validation"); Console.WriteLine("ShellCheck found, performing additional validation");
// Use ShellCheck to validate the script - exclude style/best practice warnings // Run ShellCheck to validate the script, excluding style warnings
// We want to catch actual syntax errors, not style suggestions
var shellcheckProcess = new Process(); var shellcheckProcess = new Process();
shellcheckProcess.StartInfo.FileName = "shellcheck"; shellcheckProcess.StartInfo.FileName = "shellcheck";
// Exclude various style warnings that aren't actual syntax errors // Exclude style warnings - we only care about actual errors
// SC2016: Expressions don't expand in single quotes
// SC2086: Double quote to prevent globbing and word splitting
// SC2129: Consider using { cmd1; cmd2; } >> file instead of individual redirects
// SC2181: Check exit code directly with e.g. 'if mycmd;', not indirectly with $?
// SC2094: Make sure not to read and write the same file in the same pipeline
// SC2009: Consider using pgrep instead of grepping ps output
// SC2034: Variable appears unused (false positives common)
shellcheckProcess.StartInfo.Arguments = $"-e SC2016,SC2129,SC2086,SC2181,SC2094,SC2009,SC2034 {tempScriptPath}"; shellcheckProcess.StartInfo.Arguments = $"-e SC2016,SC2129,SC2086,SC2181,SC2094,SC2009,SC2034 {tempScriptPath}";
shellcheckProcess.StartInfo.RedirectStandardOutput = true; shellcheckProcess.StartInfo.RedirectStandardOutput = true;
shellcheckProcess.StartInfo.RedirectStandardError = true; shellcheckProcess.StartInfo.RedirectStandardError = true;
shellcheckProcess.StartInfo.UseShellExecute = false; shellcheckProcess.StartInfo.UseShellExecute = false;
shellcheckProcess.Start(); shellcheckProcess.Start();
string shellcheckOutput = shellcheckProcess.StandardOutput.ReadToEnd(); string shellcheckOutput = shellcheckProcess.StandardOutput.ReadToEnd();
string shellcheckErrors = shellcheckProcess.StandardError.ReadToEnd(); string shellcheckErrors = shellcheckProcess.StandardError.ReadToEnd();
shellcheckProcess.WaitForExit(); shellcheckProcess.WaitForExit();
// If ShellCheck finds errors, fail the test // If ShellCheck finds errors, fail the test
if (shellcheckProcess.ExitCode != 0) if (shellcheckProcess.ExitCode != 0)
{ {
Console.WriteLine($"ShellCheck found syntax errors: {shellcheckOutput}"); Console.WriteLine($"ShellCheck found syntax errors: {shellcheckOutput}");
Console.WriteLine($"ShellCheck errors: {shellcheckErrors}"); Console.WriteLine($"ShellCheck errors: {shellcheckErrors}");
Assert.Fail($"ShellCheck validation failed with exit code {shellcheckProcess.ExitCode}. Output: {shellcheckOutput}. Errors: {shellcheckErrors}"); Assert.Fail($"ShellCheck validation failed with exit code {shellcheckProcess.ExitCode}. Output: {shellcheckOutput}. Errors: {shellcheckErrors}");
} }
else else
@@ -273,7 +235,7 @@ namespace GitHub.Runner.Common.Tests.Listener
{ {
Console.WriteLine("ShellCheck not found, skipping additional validation"); Console.WriteLine("ShellCheck not found, skipping additional validation");
} }
// Cleanup // Cleanup
try try
{ {
@@ -284,18 +246,18 @@ namespace GitHub.Runner.Common.Tests.Listener
// Best effort cleanup // Best effort cleanup
} }
} }
// Additional diagnostic information about bash version // Additional diagnostic information about bash version
var bashVersionProcess = new Process(); var bashVersionProcess = new Process();
bashVersionProcess.StartInfo.FileName = "bash"; bashVersionProcess.StartInfo.FileName = "bash";
bashVersionProcess.StartInfo.Arguments = "--version"; bashVersionProcess.StartInfo.Arguments = "--version";
bashVersionProcess.StartInfo.RedirectStandardOutput = true; bashVersionProcess.StartInfo.RedirectStandardOutput = true;
bashVersionProcess.StartInfo.UseShellExecute = false; bashVersionProcess.StartInfo.UseShellExecute = false;
bashVersionProcess.Start(); bashVersionProcess.Start();
string bashVersion = bashVersionProcess.StandardOutput.ReadToEnd(); string bashVersion = bashVersionProcess.StandardOutput.ReadToEnd();
bashVersionProcess.WaitForExit(); bashVersionProcess.WaitForExit();
Console.WriteLine($"Bash version: {bashVersion.Split('\n')[0]}"); Console.WriteLine($"Bash version: {bashVersion.Split('\n')[0]}");
} }
catch (Exception ex) catch (Exception ex)
@@ -304,26 +266,6 @@ namespace GitHub.Runner.Common.Tests.Listener
throw; throw;
} }
} }
private void TestSyntaxWithBash(string scriptPath, string errorType, string debugFile)
{
var process = new Process();
process.StartInfo.FileName = "bash";
process.StartInfo.Arguments = $"-n {scriptPath}";
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = false;
process.Start();
string errors = process.StandardError.ReadToEnd();
process.WaitForExit();
File.AppendAllText(debugFile, $"Testing {errorType}:\n");
File.AppendAllText(debugFile, $"Exit code: {process.ExitCode}\n");
File.AppendAllText(debugFile, $"Errors: {errors}\n");
File.AppendAllText(debugFile, $"-----------------------\n");
}
[Fact] [Fact]
[Trait("Level", "L0")] [Trait("Level", "L0")]
@@ -698,7 +640,7 @@ if exist file.txt (
} }
} }
// Helper method to check for unclosed quotes that handles escaped quotes properly // Check for unclosed quotes in script text
private bool HasUnclosedQuotes(string text) private bool HasUnclosedQuotes(string text)
{ {
bool inQuote = false; bool inQuote = false;
@@ -708,31 +650,27 @@ if exist file.txt (
{ {
char c = text[i]; char c = text[i];
// Check for escape character (backslash)
if (c == '\\') if (c == '\\')
{ {
isEscaped = !isEscaped; // Toggle escape state isEscaped = !isEscaped;
continue; continue;
} }
// Check for quotes, but only if not escaped
if (c == '"' && !isEscaped) if (c == '"' && !isEscaped)
{ {
inQuote = !inQuote; inQuote = !inQuote;
} }
// Reset escape state after non-backslash character
if (c != '\\') if (c != '\\')
{ {
isEscaped = false; isEscaped = false;
} }
} }
// If we're still in a quote at the end, there's an unclosed quote
return inQuote; return inQuote;
} }
// Helper method to check for balanced parentheses accounting for strings and comments // Check for balanced parentheses in batch scripts
private bool HasBalancedParentheses(string text) private bool HasBalancedParentheses(string text)
{ {
int balance = 0; int balance = 0;
@@ -744,7 +682,6 @@ if exist file.txt (
{ {
char c = text[i]; char c = text[i];
// Skip processing if we're in a comment (for batch files, REM or ::)
if (inComment) if (inComment)
{ {
if (c == '\n' || c == '\r') if (c == '\n' || c == '\r')
@@ -754,7 +691,6 @@ if exist file.txt (
continue; continue;
} }
// Check for comment start
if (!inQuote && i < text.Length - 1 && c == ':' && text[i+1] == ':') if (!inQuote && i < text.Length - 1 && c == ':' && text[i+1] == ':')
{ {
inComment = true; inComment = true;
@@ -768,20 +704,17 @@ if exist file.txt (
continue; continue;
} }
// Check for escape character
if (c == '\\') if (c == '\\')
{ {
isEscaped = !isEscaped; isEscaped = !isEscaped;
continue; continue;
} }
// Check for quote state
if (c == '"' && !isEscaped) if (c == '"' && !isEscaped)
{ {
inQuote = !inQuote; inQuote = !inQuote;
} }
// Only count parentheses when not in a quoted string
if (!inQuote) if (!inQuote)
{ {
if (c == '(') if (c == '(')
@@ -791,7 +724,6 @@ if exist file.txt (
else if (c == ')') else if (c == ')')
{ {
balance--; balance--;
// Negative balance means we have a closing paren without an opening one
if (balance < 0) if (balance < 0)
{ {
return false; return false;
@@ -799,14 +731,12 @@ if exist file.txt (
} }
} }
// Reset escape state
if (c != '\\') if (c != '\\')
{ {
isEscaped = false; isEscaped = false;
} }
} }
// Balanced if we end with zero
return balance == 0; return balance == 0;
} }