mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
1119 lines
48 KiB
C#
1119 lines
48 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using GitHub.Actions.RunService.WebApi;
|
|
using GitHub.Runner.Sdk;
|
|
using GitHub.Runner.Worker;
|
|
using GitHub.Runner.Worker.Container;
|
|
using GitHub.Runner.Worker.Handlers;
|
|
using Moq;
|
|
using Xunit;
|
|
using DTWebApi = GitHub.DistributedTask.WebApi;
|
|
|
|
namespace GitHub.Runner.Common.Tests.Worker
|
|
{
|
|
public sealed class OutputManagerL0
|
|
{
|
|
private Mock<IExecutionContext> _executionContext;
|
|
private Mock<IActionCommandManager> _commandManager;
|
|
private Variables _variables;
|
|
private OnMatcherChanged _onMatcherChanged;
|
|
private List<Tuple<DTWebApi.Issue, string>> _issues;
|
|
private List<string> _messages;
|
|
private List<string> _commands;
|
|
private OutputManager _outputManager;
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public void AddMatcher_Clobber()
|
|
{
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "ERROR: (.+)",
|
|
Message = 1,
|
|
},
|
|
},
|
|
},
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-2",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "NOT GOOD: (.+)",
|
|
Message = 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
using (Setup(matchers: matchers))
|
|
using (_outputManager)
|
|
{
|
|
Process("ERROR: message 1");
|
|
Process("NOT GOOD: message 2");
|
|
Add(new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "ERROR: (.+) END MESSAGE",
|
|
Message = 1,
|
|
},
|
|
},
|
|
});
|
|
Process("ERROR: message 3 END MESSAGE");
|
|
Process("ERROR: message 4");
|
|
Process("NOT GOOD: message 5");
|
|
Assert.Equal(4, _issues.Count);
|
|
Assert.Equal("message 1", _issues[0].Item1.Message);
|
|
Assert.Equal("message 2", _issues[1].Item1.Message);
|
|
Assert.Equal("message 3", _issues[2].Item1.Message);
|
|
Assert.Equal("message 5", _issues[3].Item1.Message);
|
|
Assert.Equal(0, _commands.Count);
|
|
Assert.Equal(1, _messages.Count);
|
|
Assert.Equal("ERROR: message 4", _messages[0]);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public void AddMatcher_Prepend()
|
|
{
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "ERROR: (.+)",
|
|
Message = 1,
|
|
},
|
|
},
|
|
},
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-2",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "NOT GOOD: (.+)",
|
|
Message = 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
using (Setup(matchers: matchers))
|
|
using (_outputManager)
|
|
{
|
|
Process("ERROR: message 1");
|
|
Process("NOT GOOD: message 2");
|
|
Add(new IssueMatcherConfig
|
|
{
|
|
Owner = "new-matcher",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "ERROR: (.+) END MESSAGE",
|
|
Message = 1,
|
|
},
|
|
},
|
|
});
|
|
Process("ERROR: message 3 END MESSAGE");
|
|
Process("ERROR: message 4");
|
|
Process("NOT GOOD: message 5");
|
|
Assert.Equal(5, _issues.Count);
|
|
Assert.Equal("message 1", _issues[0].Item1.Message);
|
|
Assert.Equal("message 2", _issues[1].Item1.Message);
|
|
Assert.Equal("message 3", _issues[2].Item1.Message);
|
|
Assert.Equal("message 4", _issues[3].Item1.Message);
|
|
Assert.Equal("message 5", _issues[4].Item1.Message);
|
|
Assert.Equal(0, _commands.Count);
|
|
Assert.Equal(0, _messages.Count);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public void MatcherCode()
|
|
{
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = @"(.*): (.+)",
|
|
Code = 1,
|
|
Message = 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
using (Setup(matchers: matchers))
|
|
using (_outputManager)
|
|
{
|
|
Process("BAD: real bad");
|
|
Process(": not working");
|
|
Assert.Equal(2, _issues.Count);
|
|
Assert.Equal("real bad", _issues[0].Item1.Message);
|
|
Assert.Equal("BAD", _issues[0].Item1.Data["code"]);
|
|
Assert.Equal("not working", _issues[1].Item1.Message);
|
|
Assert.False(_issues[1].Item1.Data.ContainsKey("code"));
|
|
Assert.Equal(0, _commands.Count);
|
|
Assert.Equal(0, _messages.Count);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public void DoesNotResetMatchingMatcher()
|
|
{
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "Start: .+",
|
|
},
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "Error: (.+)",
|
|
Message = 1,
|
|
Loop = true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
using (Setup(matchers: matchers))
|
|
using (_outputManager)
|
|
{
|
|
Process("Start: hello");
|
|
Process("Error: it broke");
|
|
Process("Error: oh no");
|
|
Process("Error: not good");
|
|
Process("regular message 1");
|
|
Process("Start: hello again");
|
|
Process("Error: it broke again");
|
|
Process("Error: real bad");
|
|
Process("regular message 2");
|
|
Assert.Equal(5, _issues.Count);
|
|
Assert.Equal("it broke", _issues[0].Item1.Message);
|
|
Assert.Equal("oh no", _issues[1].Item1.Message);
|
|
Assert.Equal("not good", _issues[2].Item1.Message);
|
|
Assert.Equal("it broke again", _issues[3].Item1.Message);
|
|
Assert.Equal("real bad", _issues[4].Item1.Message);
|
|
Assert.Equal(0, _commands.Count);
|
|
Assert.Equal(4, _messages.Count);
|
|
Assert.Equal("Start: hello", _messages[0]);
|
|
Assert.Equal("regular message 1", _messages[1]);
|
|
Assert.Equal("Start: hello again", _messages[2]);
|
|
Assert.Equal("regular message 2", _messages[3]);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public void InitialMatchers()
|
|
{
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "ERROR: (.+)",
|
|
Message = 1,
|
|
},
|
|
},
|
|
},
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-2",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "NOT GOOD: (.+)",
|
|
Message = 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
using (Setup(matchers: matchers))
|
|
using (_outputManager)
|
|
{
|
|
Process("ERROR: it is broken");
|
|
Process("NOT GOOD: that did not work");
|
|
Assert.Equal(2, _issues.Count);
|
|
Assert.Equal("it is broken", _issues[0].Item1.Message);
|
|
Assert.Equal("that did not work", _issues[1].Item1.Message);
|
|
Assert.Equal(0, _commands.Count);
|
|
Assert.Equal(0, _messages.Count);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public void MatcherLineColumn()
|
|
{
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = @"\((.+),(.+)\): (.+)",
|
|
Line = 1,
|
|
Column = 2,
|
|
Message = 3,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
using (Setup(matchers: matchers))
|
|
using (_outputManager)
|
|
{
|
|
Process("(12,34): real bad");
|
|
Process("(12,thirty-four): it is broken");
|
|
Process("(twelve,34): not working");
|
|
Assert.Equal(3, _issues.Count);
|
|
Assert.Equal("real bad", _issues[0].Item1.Message);
|
|
Assert.Equal("12", _issues[0].Item1.Data["line"]);
|
|
Assert.Equal("34", _issues[0].Item1.Data["col"]);
|
|
Assert.Equal("it is broken", _issues[1].Item1.Message);
|
|
Assert.Equal("12", _issues[1].Item1.Data["line"]);
|
|
Assert.False(_issues[1].Item1.Data.ContainsKey("col"));
|
|
Assert.Equal("not working", _issues[2].Item1.Message);
|
|
Assert.False(_issues[2].Item1.Data.ContainsKey("line"));
|
|
Assert.Equal("34", _issues[2].Item1.Data["col"]);
|
|
Assert.Equal(0, _commands.Count);
|
|
Assert.Equal(2, _messages.Count);
|
|
Assert.Equal("##[debug]Unable to parse column number 'thirty-four'", _messages[0]);
|
|
Assert.Equal("##[debug]Unable to parse line number 'twelve'", _messages[1]);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public void MatcherDoesNotReceiveCommand()
|
|
{
|
|
using (Setup())
|
|
using (_outputManager)
|
|
{
|
|
Add(new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "ERROR: (.+)",
|
|
Message = 1,
|
|
},
|
|
},
|
|
});
|
|
Process("this line is an ERROR: it is broken");
|
|
Process("##[some-command]this line is a command even though it contains ERROR: not working");
|
|
Process("this line is a command too ##[some-command]even though it contains ERROR: not working again");
|
|
Process("##[not-command]this line is an ERROR: it is broken again");
|
|
Assert.Equal(2, _issues.Count);
|
|
Assert.Equal("it is broken", _issues[0].Item1.Message);
|
|
Assert.Equal("it is broken again", _issues[1].Item1.Message);
|
|
Assert.Equal(2, _commands.Count);
|
|
Assert.Equal("##[some-command]this line is a command even though it contains ERROR: not working", _commands[0]);
|
|
Assert.Equal("this line is a command too ##[some-command]even though it contains ERROR: not working again", _commands[1]);
|
|
Assert.Equal(0, _messages.Count);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public void MatcherRemoveColorCodes()
|
|
{
|
|
using (Setup())
|
|
using (_outputManager)
|
|
{
|
|
Add(new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "^the error: (.+)$",
|
|
Message = 1,
|
|
},
|
|
},
|
|
});
|
|
Process("the error: \033[31mred, \033[1;31mbright red, \033[mreset");
|
|
Assert.Equal(1, _issues.Count);
|
|
Assert.Equal("red, bright red, reset", _issues[0].Item1.Message);
|
|
Assert.Equal("the error: red, bright red, reset", _issues[0].Item2);
|
|
Assert.Equal(0, _commands.Count);
|
|
Assert.Equal(0, _messages.Count);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public void RemoveMatcher()
|
|
{
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "ERROR: (.+)",
|
|
Message = 1,
|
|
},
|
|
},
|
|
},
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-2",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "NOT GOOD: (.+)",
|
|
Message = 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
using (Setup(matchers: matchers))
|
|
using (_outputManager)
|
|
{
|
|
Process("ERROR: message 1");
|
|
Process("NOT GOOD: message 2");
|
|
Remove("my-matcher-1");
|
|
Process("ERROR: message 3");
|
|
Process("NOT GOOD: message 4");
|
|
Assert.Equal(3, _issues.Count);
|
|
Assert.Equal("message 1", _issues[0].Item1.Message);
|
|
Assert.Equal("message 2", _issues[1].Item1.Message);
|
|
Assert.Equal("message 4", _issues[2].Item1.Message);
|
|
Assert.Equal(0, _commands.Count);
|
|
Assert.Equal(1, _messages.Count);
|
|
Assert.Equal("ERROR: message 3", _messages[0]);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public void ResetsOtherMatchers()
|
|
{
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "Matches both line 1: .+",
|
|
},
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "Matches 1 only line 2: (.+)",
|
|
Message = 1,
|
|
},
|
|
},
|
|
},
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-2",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "Matches both line 1: (.+)",
|
|
},
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "(.+)",
|
|
Message = 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
using (Setup(matchers: matchers))
|
|
using (_outputManager)
|
|
{
|
|
Process("Matches both line 1: hello");
|
|
Process("Matches 1 only line 2: it broke");
|
|
Process("regular message 1");
|
|
Process("regular message 2");
|
|
Process("Matches both line 1: hello again");
|
|
Process("oh no, another error");
|
|
Assert.Equal(2, _issues.Count);
|
|
Assert.Equal("it broke", _issues[0].Item1.Message);
|
|
Assert.Equal("oh no, another error", _issues[1].Item1.Message);
|
|
Assert.Equal(0, _commands.Count);
|
|
Assert.Equal(4, _messages.Count);
|
|
Assert.Equal("Matches both line 1: hello", _messages[0]);
|
|
Assert.Equal("regular message 1", _messages[1]);
|
|
Assert.Equal("regular message 2", _messages[2]);
|
|
Assert.Equal("Matches both line 1: hello again", _messages[3]);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public void MatcherSeverity()
|
|
{
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "(.*): (.+)",
|
|
Severity = 1,
|
|
Message = 2,
|
|
},
|
|
},
|
|
},
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-2",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = "ERROR! (.+)",
|
|
Message = 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
using (Setup(matchers: matchers))
|
|
using (_outputManager)
|
|
{
|
|
Process("ERRor: real bad");
|
|
Process("WARNing: not great");
|
|
Process("info: hey");
|
|
Process(": not working");
|
|
Process("ERROR! uh oh");
|
|
Assert.Equal(4, _issues.Count);
|
|
Assert.Equal("real bad", _issues[0].Item1.Message);
|
|
Assert.Equal(DTWebApi.IssueType.Error, _issues[0].Item1.Type);
|
|
Assert.Equal("not great", _issues[1].Item1.Message);
|
|
Assert.Equal(DTWebApi.IssueType.Warning, _issues[1].Item1.Type);
|
|
Assert.Equal("not working", _issues[2].Item1.Message);
|
|
Assert.Equal(DTWebApi.IssueType.Error, _issues[2].Item1.Type);
|
|
Assert.Equal("uh oh", _issues[3].Item1.Message);
|
|
Assert.Equal(DTWebApi.IssueType.Error, _issues[3].Item1.Type);
|
|
Assert.Equal(0, _commands.Count);
|
|
Assert.Equal(2, _messages.Count);
|
|
Assert.StartsWith("##[debug]Skipped", _messages[0]);
|
|
Assert.Contains("'info'", _messages[0]);
|
|
Assert.Equal("info: hey", _messages[1]);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public void MatcherTimeout()
|
|
{
|
|
Environment.SetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_ISSUE_MATCHER_TIMEOUT", "0:0:0.01");
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "email",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = @"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$",
|
|
Message = 0,
|
|
},
|
|
},
|
|
},
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "err",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = @"ERR: (.+)",
|
|
Message = 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
using (Setup(matchers: matchers))
|
|
using (_outputManager)
|
|
{
|
|
Process("john.doe@contoso.com");
|
|
Process("t@t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.c%20");
|
|
Process("jane.doe@contoso.com");
|
|
Process("ERR: this error");
|
|
Assert.Equal(3, _issues.Count);
|
|
Assert.Equal("john.doe@contoso.com", _issues[0].Item1.Message);
|
|
Assert.Contains("Removing issue matcher 'email'", _issues[1].Item1.Message);
|
|
Assert.Equal("this error", _issues[2].Item1.Message);
|
|
Assert.Equal(0, _commands.Count);
|
|
Assert.Equal(2, _messages.Where(x => x.StartsWith("##[debug]Timeout processing issue matcher")).Count());
|
|
Assert.Equal(1, _messages.Where(x => x.Equals("t@t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.c%20")).Count());
|
|
Assert.Equal(1, _messages.Where(x => x.StartsWith("jane.doe@contoso.com")).Count());
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public async void MatcherFile()
|
|
{
|
|
Environment.SetEnvironmentVariable("RUNNER_TEST_GET_REPOSITORY_PATH_FAILSAFE", "2");
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = @"(.+): (.+)",
|
|
File = 1,
|
|
Message = 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
using (var hostContext = Setup(matchers: matchers))
|
|
using (_outputManager)
|
|
{
|
|
// Setup github.workspace, github.repository
|
|
var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work);
|
|
ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory));
|
|
Directory.CreateDirectory(workDirectory);
|
|
var workspaceDirectory = Path.Combine(workDirectory, "workspace");
|
|
Directory.CreateDirectory(workspaceDirectory);
|
|
_executionContext.Setup(x => x.GetGitHubContext("workspace")).Returns(workspaceDirectory);
|
|
_executionContext.Setup(x => x.GetGitHubContext("repository")).Returns("my-org/workflow-repo");
|
|
|
|
// Setup some git repositories
|
|
// <WORKSPACE>/workflow-repo
|
|
// <WORKSPACE>/workflow-repo/nested-other-repo
|
|
// <WORKSPACE>/other-repo
|
|
// <WORKSPACE>/other-repo/nested-workflow-repo
|
|
// <WORKSPACE>/workflow-repo-using-ssh
|
|
var workflowRepository = Path.Combine(workspaceDirectory, "workflow-repo");
|
|
var nestedOtherRepository = Path.Combine(workspaceDirectory, "workflow-repo", "nested-other-repo");
|
|
var otherRepository = Path.Combine(workspaceDirectory, workflowRepository, "nested-other-repo");
|
|
var nestedWorkflowRepository = Path.Combine(workspaceDirectory, "other-repo", "nested-workflow-repo");
|
|
var workflowRepositoryUsingSsh = Path.Combine(workspaceDirectory, "workflow-repo-using-ssh");
|
|
await CreateRepository(hostContext, workflowRepository, "https://github.com/my-org/workflow-repo");
|
|
await CreateRepository(hostContext, nestedOtherRepository, "https://github.com/my-org/other-repo");
|
|
await CreateRepository(hostContext, otherRepository, "https://github.com/my-org/other-repo");
|
|
await CreateRepository(hostContext, nestedWorkflowRepository, "https://github.com/my-org/workflow-repo");
|
|
await CreateRepository(hostContext, workflowRepositoryUsingSsh, "git@github.com:my-org/workflow-repo.git");
|
|
|
|
// Create test files
|
|
var file_noRepository = Path.Combine(workspaceDirectory, "no-repo.txt");
|
|
var file_workflowRepository = Path.Combine(workflowRepository, "workflow-repo.txt");
|
|
var file_workflowRepository_nestedDirectory = Path.Combine(workflowRepository, "subdir", "subdir2", "workflow-repo-nested-dir.txt");
|
|
var file_workflowRepository_failsafe = Path.Combine(workflowRepository, "failsafe-subdir", "failsafe-subdir2", "failsafe-subdir3", "workflow-repo-failsafe.txt");
|
|
var file_nestedOtherRepository = Path.Combine(nestedOtherRepository, "nested-other-repo");
|
|
var file_otherRepository = Path.Combine(otherRepository, "other-repo.txt");
|
|
var file_nestedWorkflowRepository = Path.Combine(nestedWorkflowRepository, "nested-workflow-repo.txt");
|
|
var file_workflowRepositoryUsingSsh = Path.Combine(workflowRepositoryUsingSsh, "workflow-repo-using-ssh.txt");
|
|
foreach (var file in new[] { file_noRepository, file_workflowRepository, file_workflowRepository_nestedDirectory, file_workflowRepository_failsafe, file_nestedOtherRepository, file_otherRepository, file_nestedWorkflowRepository, file_workflowRepositoryUsingSsh })
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(file));
|
|
File.WriteAllText(file, "");
|
|
}
|
|
|
|
// Process
|
|
Process($"{file_noRepository}: some error 1");
|
|
Process($"{file_workflowRepository}: some error 2");
|
|
Process($"{file_workflowRepository.Substring(workspaceDirectory.Length + 1)}: some error 3"); // Relative path from workspace dir
|
|
Process($"{file_workflowRepository_nestedDirectory}: some error 4");
|
|
Process($"{file_workflowRepository_failsafe}: some error 5");
|
|
Process($"{file_nestedOtherRepository}: some error 6");
|
|
Process($"{file_otherRepository}: some error 7");
|
|
Process($"{file_nestedWorkflowRepository}: some error 8");
|
|
Process($"{file_workflowRepositoryUsingSsh}: some error 9");
|
|
|
|
Assert.Equal(9, _issues.Count);
|
|
|
|
Assert.Equal("some error 1", _issues[0].Item1.Message);
|
|
Assert.False(_issues[0].Item1.Data.ContainsKey("file"));
|
|
|
|
Assert.Equal("some error 2", _issues[1].Item1.Message);
|
|
Assert.Equal(file_workflowRepository.Substring(workflowRepository.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[1].Item1.Data["file"]);
|
|
|
|
Assert.Equal("some error 3", _issues[2].Item1.Message);
|
|
Assert.Equal(file_workflowRepository.Substring(workflowRepository.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[2].Item1.Data["file"]);
|
|
|
|
Assert.Equal("some error 4", _issues[3].Item1.Message);
|
|
Assert.Equal(file_workflowRepository_nestedDirectory.Substring(workflowRepository.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[3].Item1.Data["file"]);
|
|
|
|
Assert.Equal("some error 5", _issues[4].Item1.Message);
|
|
Assert.False(_issues[4].Item1.Data.ContainsKey("file"));
|
|
|
|
Assert.Equal("some error 6", _issues[5].Item1.Message);
|
|
Assert.False(_issues[5].Item1.Data.ContainsKey("file"));
|
|
|
|
Assert.Equal("some error 7", _issues[6].Item1.Message);
|
|
Assert.False(_issues[6].Item1.Data.ContainsKey("file"));
|
|
|
|
Assert.Equal("some error 8", _issues[7].Item1.Message);
|
|
Assert.Equal(file_nestedWorkflowRepository.Substring(nestedWorkflowRepository.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[7].Item1.Data["file"]);
|
|
|
|
Assert.Equal("some error 9", _issues[8].Item1.Message);
|
|
Assert.Equal(file_workflowRepositoryUsingSsh.Substring(workflowRepositoryUsingSsh.Length + 1).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), _issues[8].Item1.Data["file"]);
|
|
}
|
|
|
|
Environment.SetEnvironmentVariable("RUNNER_TEST_GET_REPOSITORY_PATH_FAILSAFE", "");
|
|
}
|
|
|
|
#if OS_LINUX
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public async void MatcherFile_JobContainer()
|
|
{
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = @"(.+): (.+)",
|
|
File = 1,
|
|
Message = 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
var container = new ContainerInfo();
|
|
using (var hostContext = Setup(matchers: matchers, jobContainer: container))
|
|
using (_outputManager)
|
|
{
|
|
// Setup github.workspace, github.repository
|
|
var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work);
|
|
ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory));
|
|
Directory.CreateDirectory(workDirectory);
|
|
var workspaceDirectory = Path.Combine(workDirectory, "workspace");
|
|
Directory.CreateDirectory(workspaceDirectory);
|
|
_executionContext.Setup(x => x.GetGitHubContext("workspace")).Returns(workspaceDirectory);
|
|
_executionContext.Setup(x => x.GetGitHubContext("repository")).Returns("my-org/workflow-repo");
|
|
|
|
// Setup a git repository
|
|
await CreateRepository(hostContext, workspaceDirectory, "https://github.com/my-org/workflow-repo");
|
|
|
|
// Create test files
|
|
var file = Path.Combine(workspaceDirectory, "some-file.txt");
|
|
File.WriteAllText(file, "");
|
|
|
|
// Add translation path
|
|
container.AddPathTranslateMapping(workspaceDirectory, "/container/path/to/workspace");
|
|
|
|
// Process
|
|
Process($"/container/path/to/workspace/some-file.txt: some error 1");
|
|
Process($"some-file.txt: some error 2");
|
|
|
|
Assert.Equal(2, _issues.Count);
|
|
|
|
Assert.Equal("some error 1", _issues[0].Item1.Message);
|
|
Assert.Equal("some-file.txt", _issues[0].Item1.Data["file"]);
|
|
|
|
Assert.Equal("some error 2", _issues[1].Item1.Message);
|
|
Assert.Equal("some-file.txt", _issues[1].Item1.Data["file"]);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public async void MatcherFile_StepContainer()
|
|
{
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = @"(.+): (.+)",
|
|
File = 1,
|
|
Message = 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
var container = new ContainerInfo();
|
|
using (var hostContext = Setup(matchers: matchers, stepContainer: container))
|
|
using (_outputManager)
|
|
{
|
|
// Setup github.workspace, github.repository
|
|
var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work);
|
|
ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory));
|
|
Directory.CreateDirectory(workDirectory);
|
|
var workspaceDirectory = Path.Combine(workDirectory, "workspace");
|
|
Directory.CreateDirectory(workspaceDirectory);
|
|
_executionContext.Setup(x => x.GetGitHubContext("workspace")).Returns(workspaceDirectory);
|
|
_executionContext.Setup(x => x.GetGitHubContext("repository")).Returns("my-org/workflow-repo");
|
|
|
|
// Setup a git repository
|
|
await CreateRepository(hostContext, workspaceDirectory, "https://github.com/my-org/workflow-repo");
|
|
|
|
// Create test files
|
|
var file = Path.Combine(workspaceDirectory, "some-file.txt");
|
|
File.WriteAllText(file, "");
|
|
|
|
// Add translation path
|
|
container.AddPathTranslateMapping(workspaceDirectory, "/container/path/to/workspace");
|
|
|
|
// Process
|
|
Process($"/container/path/to/workspace/some-file.txt: some error 1");
|
|
Process($"some-file.txt: some error 2");
|
|
|
|
Assert.Equal(2, _issues.Count);
|
|
|
|
Assert.Equal("some error 1", _issues[0].Item1.Message);
|
|
Assert.Equal("some-file.txt", _issues[0].Item1.Data["file"]);
|
|
|
|
Assert.Equal("some error 2", _issues[1].Item1.Message);
|
|
Assert.Equal("some-file.txt", _issues[1].Item1.Data["file"]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public async void MatcherFromPath()
|
|
{
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = @"(.+): (.+) \[(.+)\]",
|
|
File = 1,
|
|
Message = 2,
|
|
FromPath = 3,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
using (var hostContext = Setup(matchers: matchers))
|
|
using (_outputManager)
|
|
{
|
|
// Setup github.workspace, github.repository
|
|
var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work);
|
|
ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory));
|
|
Directory.CreateDirectory(workDirectory);
|
|
var workspaceDirectory = Path.Combine(workDirectory, "workspace");
|
|
Directory.CreateDirectory(workspaceDirectory);
|
|
_executionContext.Setup(x => x.GetGitHubContext("workspace")).Returns(workspaceDirectory);
|
|
_executionContext.Setup(x => x.GetGitHubContext("repository")).Returns("my-org/workflow-repo");
|
|
|
|
// Setup a git repository
|
|
var repositoryPath = Path.Combine(workspaceDirectory, "workflow-repo");
|
|
await CreateRepository(hostContext, repositoryPath, "https://github.com/my-org/workflow-repo");
|
|
|
|
// Create a test file
|
|
var filePath = Path.Combine(repositoryPath, "some-project", "some-directory", "some-file.txt");
|
|
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
|
|
File.WriteAllText(filePath, "");
|
|
|
|
// Process
|
|
Process("some-directory/some-file.txt: some error [workflow-repo/some-project/some-project.proj]");
|
|
Assert.Equal(1, _issues.Count);
|
|
Assert.Equal("some error", _issues[0].Item1.Message);
|
|
Assert.Equal("some-project/some-directory/some-file.txt", _issues[0].Item1.Data["file"]);
|
|
Assert.Equal(0, _commands.Count);
|
|
Assert.Equal(0, _messages.Count);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public async void MatcherDefaultFromPath()
|
|
{
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
new IssueMatcherConfig
|
|
{
|
|
Owner = "my-matcher-1",
|
|
FromPath = "workflow-repo/some-project/some-project.proj",
|
|
Patterns = new[]
|
|
{
|
|
new IssuePatternConfig
|
|
{
|
|
Pattern = @"(.+): (.+)",
|
|
File = 1,
|
|
Message = 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
using (var hostContext = Setup(matchers: matchers))
|
|
using (_outputManager)
|
|
{
|
|
// Setup github.workspace, github.repository
|
|
var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work);
|
|
ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory));
|
|
Directory.CreateDirectory(workDirectory);
|
|
var workspaceDirectory = Path.Combine(workDirectory, "workspace");
|
|
Directory.CreateDirectory(workspaceDirectory);
|
|
_executionContext.Setup(x => x.GetGitHubContext("workspace")).Returns(workspaceDirectory);
|
|
_executionContext.Setup(x => x.GetGitHubContext("repository")).Returns("my-org/workflow-repo");
|
|
|
|
// Setup a git repository
|
|
var repositoryPath = Path.Combine(workspaceDirectory, "workflow-repo");
|
|
await CreateRepository(hostContext, repositoryPath, "https://github.com/my-org/workflow-repo");
|
|
|
|
// Create a test file
|
|
var filePath = Path.Combine(repositoryPath, "some-project", "some-directory", "some-file.txt");
|
|
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
|
|
File.WriteAllText(filePath, "");
|
|
|
|
// Process
|
|
Process("some-directory/some-file.txt: some error");
|
|
Assert.Equal(1, _issues.Count);
|
|
Assert.Equal("some error", _issues[0].Item1.Message);
|
|
Assert.Equal("some-project/some-directory/some-file.txt", _issues[0].Item1.Data["file"]);
|
|
Assert.Equal(0, _commands.Count);
|
|
Assert.Equal(0, _messages.Count);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
[Trait("Level", "L0")]
|
|
[Trait("Category", "Worker")]
|
|
public void CaptureTelemetryForGitUnsafeRepository()
|
|
{
|
|
using (Setup())
|
|
using (_outputManager)
|
|
{
|
|
Process("fatal: unsafe repository ('/github/workspace' is owned by someone else)");
|
|
Assert.Contains("fatal: unsafe repository ('/github/workspace' is owned by someone else)", _executionContext.Object.StepTelemetry.ErrorMessages);
|
|
}
|
|
}
|
|
|
|
private TestHostContext Setup(
|
|
[CallerMemberName] string name = "",
|
|
IssueMatchersConfig matchers = null,
|
|
ContainerInfo jobContainer = null,
|
|
ContainerInfo stepContainer = null)
|
|
{
|
|
matchers?.Validate();
|
|
|
|
_onMatcherChanged = null;
|
|
_issues = new List<Tuple<DTWebApi.Issue, string>>();
|
|
_messages = new List<string>();
|
|
_commands = new List<string>();
|
|
|
|
var hostContext = new TestHostContext(this, name);
|
|
|
|
_variables = new Variables(hostContext, new Dictionary<string, DTWebApi.VariableValue>());
|
|
|
|
_executionContext = new Mock<IExecutionContext>();
|
|
_executionContext.Setup(x => x.Global)
|
|
.Returns(new GlobalContext
|
|
{
|
|
Container = jobContainer,
|
|
Variables = _variables,
|
|
WriteDebug = true,
|
|
});
|
|
_executionContext.Setup(x => x.StepTelemetry)
|
|
.Returns(new DTWebApi.ActionsStepTelemetry());
|
|
_executionContext.Setup(x => x.GetMatchers())
|
|
.Returns(matchers?.Matchers ?? new List<IssueMatcherConfig>());
|
|
_executionContext.Setup(x => x.Add(It.IsAny<OnMatcherChanged>()))
|
|
.Callback((OnMatcherChanged handler) =>
|
|
{
|
|
_onMatcherChanged = handler;
|
|
});
|
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
|
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
|
{
|
|
var resolvedMessage = issue.Message;
|
|
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
|
{
|
|
resolvedMessage = logOptions.LogMessageOverride;
|
|
}
|
|
_issues.Add(new(issue, resolvedMessage));
|
|
});
|
|
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
|
.Callback((string tag, string message) =>
|
|
{
|
|
_messages.Add($"{tag}{message}");
|
|
hostContext.GetTrace().Info($"{tag}{message}");
|
|
});
|
|
|
|
_commandManager = new Mock<IActionCommandManager>();
|
|
_commandManager.Setup(x => x.TryProcessCommand(It.IsAny<IExecutionContext>(), It.IsAny<string>(), It.IsAny<ContainerInfo>()))
|
|
.Returns((IExecutionContext executionContext, string line, ContainerInfo container) =>
|
|
{
|
|
if (line.IndexOf("##[some-command]") >= 0)
|
|
{
|
|
_commands.Add(line);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
_outputManager = new OutputManager(_executionContext.Object, _commandManager.Object, stepContainer);
|
|
return hostContext;
|
|
}
|
|
|
|
private void Add(IssueMatcherConfig matcher)
|
|
{
|
|
var matchers = new IssueMatchersConfig
|
|
{
|
|
Matchers =
|
|
{
|
|
matcher,
|
|
},
|
|
};
|
|
matchers.Validate();
|
|
_onMatcherChanged(null, new MatcherChangedEventArgs(matcher));
|
|
}
|
|
|
|
private void Remove(string owner)
|
|
{
|
|
var matcher = new IssueMatcherConfig { Owner = owner };
|
|
_onMatcherChanged(null, new MatcherChangedEventArgs(matcher));
|
|
}
|
|
|
|
private void Process(string line)
|
|
{
|
|
_outputManager.OnDataReceived(null, new ProcessDataReceivedEventArgs(line));
|
|
}
|
|
|
|
private async Task CreateRepository(TestHostContext hostConetxt, string path, string url)
|
|
{
|
|
Directory.CreateDirectory(path);
|
|
var gitPath = WhichUtil.Which("git", true);
|
|
var environment = new Dictionary<string, string>();
|
|
|
|
using (var processInvoker = new ProcessInvoker(hostConetxt.GetTrace()))
|
|
{
|
|
await processInvoker.ExecuteAsync(path, gitPath, "init", environment, CancellationToken.None);
|
|
}
|
|
|
|
using (var processInvoker = new ProcessInvoker(hostConetxt.GetTrace()))
|
|
{
|
|
await processInvoker.ExecuteAsync(path, gitPath, $"remote add origin {url}", environment, CancellationToken.None);
|
|
}
|
|
}
|
|
}
|
|
}
|