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
{
// 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)
{
// Skip on Windows
@@ -46,28 +45,15 @@ namespace GitHub.Runner.Common.Tests.Listener
// Read the template
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)
if (templateModifier != null)
{
template = templateModifier(template);
File.AppendAllText(debugLogPath, $"Template was modified by templateModifier\n");
}
// Replace common placeholders with valid test values
string rootFolder = useFullPath ? Path.GetDirectoryName(templatePath) : Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), ".."));
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
File.WriteAllText(tempScriptPath, template);
@@ -87,16 +73,11 @@ namespace GitHub.Runner.Common.Tests.Listener
bashCheckProcess.StartInfo.RedirectStandardError = true;
bashCheckProcess.StartInfo.UseShellExecute = false;
File.AppendAllText(debugLogPath, $"Running bash check: bash -n {tempScriptPath}\n");
bashCheckProcess.Start();
string bashCheckOutput = bashCheckProcess.StandardOutput.ReadToEnd();
string bashCheckErrors = bashCheckProcess.StandardError.ReadToEnd();
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
var process = new Process();
process.StartInfo.FileName = "bash";
@@ -104,24 +85,15 @@ namespace GitHub.Runner.Common.Tests.Listener
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = false;
Console.WriteLine($"Executing: bash -n {tempScriptPath}");
File.AppendAllText(debugLogPath, $"Executing main test: bash -n {tempScriptPath}\n");
process.Start();
string errors = process.StandardError.ReadToEnd();
process.WaitForExit();
Console.WriteLine($"Process exited with code: {process.ExitCode}");
File.AppendAllText(debugLogPath, $"Process exit code: {process.ExitCode}\n");
if (!string.IsNullOrEmpty(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
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)
{
// Replace common placeholders
template = template.Replace("_PROCESS_ID_", "1234");
template = template.Replace("_RUNNER_PROCESS_NAME_", "Runner.Listener");
template = template.Replace("_ROOT_FOLDER_", rootDirectory);
@@ -186,82 +156,74 @@ namespace GitHub.Runner.Common.Tests.Listener
[Trait("SkipOn", "windows")]
public void UpdateShTemplateHasValidSyntax()
{
// Add debugging info
Console.WriteLine($"Running on platform: {RuntimeInformation.OSDescription}, Architecture: {RuntimeInformation.OSArchitecture}");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}
try
{
using (var hc = new TestHostContext(this))
{
// First validate with bash -n
ValidateShellScriptTemplateSyntax("src/Misc/layoutbin", "update.sh.template");
// Additional validation with ShellCheck if available
string rootDirectory = Path.GetFullPath(Path.Combine(TestUtil.GetSrcPath(), ".."));
string templatePath = Path.Combine(rootDirectory, "src/Misc/layoutbin", "update.sh.template");
string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempDir);
string tempScriptPath = Path.Combine(tempDir, Path.GetFileNameWithoutExtension("update.sh.template"));
// Read the template
string template = File.ReadAllText(templatePath);
// Replace placeholders with valid test values
template = ReplaceCommonPlaceholders(template, rootDirectory, tempDir);
// Write the processed template to a temporary file
File.WriteAllText(tempScriptPath, template);
// Make the file executable
var chmodProcess = new Process();
chmodProcess.StartInfo.FileName = "chmod";
chmodProcess.StartInfo.Arguments = $"+x {tempScriptPath}";
chmodProcess.Start();
chmodProcess.WaitForExit();
// Check if ShellCheck is available
var shellcheckExistsProcess = new Process();
shellcheckExistsProcess.StartInfo.FileName = "which";
shellcheckExistsProcess.StartInfo.Arguments = "shellcheck";
shellcheckExistsProcess.StartInfo.RedirectStandardOutput = true;
shellcheckExistsProcess.StartInfo.UseShellExecute = false;
shellcheckExistsProcess.Start();
string shellcheckPath = shellcheckExistsProcess.StandardOutput.ReadToEnd().Trim();
shellcheckExistsProcess.WaitForExit();
if (!string.IsNullOrEmpty(shellcheckPath))
{
Console.WriteLine("ShellCheck found, performing additional validation");
// Use ShellCheck to validate the script - exclude style/best practice warnings
// We want to catch actual syntax errors, not style suggestions
// Run ShellCheck to validate the script, excluding style warnings
var shellcheckProcess = new Process();
shellcheckProcess.StartInfo.FileName = "shellcheck";
// Exclude various style warnings that aren't actual syntax 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)
// Exclude style warnings - we only care about actual errors
shellcheckProcess.StartInfo.Arguments = $"-e SC2016,SC2129,SC2086,SC2181,SC2094,SC2009,SC2034 {tempScriptPath}";
shellcheckProcess.StartInfo.RedirectStandardOutput = true;
shellcheckProcess.StartInfo.RedirectStandardError = true;
shellcheckProcess.StartInfo.UseShellExecute = false;
shellcheckProcess.Start();
string shellcheckOutput = shellcheckProcess.StandardOutput.ReadToEnd();
string shellcheckErrors = shellcheckProcess.StandardError.ReadToEnd();
shellcheckProcess.WaitForExit();
// If ShellCheck finds errors, fail the test
if (shellcheckProcess.ExitCode != 0)
{
Console.WriteLine($"ShellCheck found syntax errors: {shellcheckOutput}");
Console.WriteLine($"ShellCheck errors: {shellcheckErrors}");
Assert.Fail($"ShellCheck validation failed with exit code {shellcheckProcess.ExitCode}. Output: {shellcheckOutput}. Errors: {shellcheckErrors}");
}
else
@@ -273,7 +235,7 @@ namespace GitHub.Runner.Common.Tests.Listener
{
Console.WriteLine("ShellCheck not found, skipping additional validation");
}
// Cleanup
try
{
@@ -284,18 +246,18 @@ namespace GitHub.Runner.Common.Tests.Listener
// Best effort cleanup
}
}
// Additional diagnostic information about bash version
var bashVersionProcess = new Process();
bashVersionProcess.StartInfo.FileName = "bash";
bashVersionProcess.StartInfo.Arguments = "--version";
bashVersionProcess.StartInfo.RedirectStandardOutput = true;
bashVersionProcess.StartInfo.UseShellExecute = false;
bashVersionProcess.Start();
string bashVersion = bashVersionProcess.StandardOutput.ReadToEnd();
bashVersionProcess.WaitForExit();
Console.WriteLine($"Bash version: {bashVersion.Split('\n')[0]}");
}
catch (Exception ex)
@@ -304,26 +266,6 @@ namespace GitHub.Runner.Common.Tests.Listener
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]
[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)
{
bool inQuote = false;
@@ -708,31 +650,27 @@ if exist file.txt (
{
char c = text[i];
// Check for escape character (backslash)
if (c == '\\')
{
isEscaped = !isEscaped; // Toggle escape state
isEscaped = !isEscaped;
continue;
}
// Check for quotes, but only if not escaped
if (c == '"' && !isEscaped)
{
inQuote = !inQuote;
}
// Reset escape state after non-backslash character
if (c != '\\')
{
isEscaped = false;
}
}
// If we're still in a quote at the end, there's an unclosed quote
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)
{
int balance = 0;
@@ -744,7 +682,6 @@ if exist file.txt (
{
char c = text[i];
// Skip processing if we're in a comment (for batch files, REM or ::)
if (inComment)
{
if (c == '\n' || c == '\r')
@@ -754,7 +691,6 @@ if exist file.txt (
continue;
}
// Check for comment start
if (!inQuote && i < text.Length - 1 && c == ':' && text[i+1] == ':')
{
inComment = true;
@@ -768,20 +704,17 @@ if exist file.txt (
continue;
}
// Check for escape character
if (c == '\\')
{
isEscaped = !isEscaped;
continue;
}
// Check for quote state
if (c == '"' && !isEscaped)
{
inQuote = !inQuote;
}
// Only count parentheses when not in a quoted string
if (!inQuote)
{
if (c == '(')
@@ -791,7 +724,6 @@ if exist file.txt (
else if (c == ')')
{
balance--;
// Negative balance means we have a closing paren without an opening one
if (balance < 0)
{
return false;
@@ -799,14 +731,12 @@ if exist file.txt (
}
}
// Reset escape state
if (c != '\\')
{
isEscaped = false;
}
}
// Balanced if we end with zero
return balance == 0;
}