New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Properly Consider WarningsAsMessages In TaskLoggingHelper's HasLoggedErrors
#6308
Conversation
… the logging service and taskloggingcontext
HasLoggedErrors
HasLoggedErrors
if (WarningsAsErrors == null && (_warningsAsErrorsByProject == null || !_warningsAsErrorsByProject.ContainsKey(key))) | ||
{ | ||
return null; | ||
} | ||
|
||
HashSet<string> allWarningsAsErrors = new HashSet<string>(); | ||
|
||
if (WarningsAsErrors != null) | ||
{ | ||
allWarningsAsErrors.UnionWith(WarningsAsErrors); | ||
} | ||
|
||
if (_warningsAsErrorsByProject != null) | ||
{ | ||
if (_warningsAsErrorsByProject.TryGetValue(key, out ISet<string> warningsAsErrors)) | ||
{ | ||
allWarningsAsErrors.UnionWith(warningsAsErrors); | ||
} | ||
} | ||
|
||
return allWarningsAsErrors; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be simplified a good bit. Also, if they're both null, we should return null and not an empty HashSet, right? Otherwise, we'd be indicating that all warnings should be counted as errors. I'd recommend something similar below. Also, since they're essentially the same method, it seems a lot easier to make them one method and call it with slightly different parameters. (You don't have to change the public API for that, since you can make the heart of the implementation private.)
if (WarningsAsErrors == null && (_warningsAsErrorsByProject == null || !_warningsAsErrorsByProject.ContainsKey(key))) | |
{ | |
return null; | |
} | |
HashSet<string> allWarningsAsErrors = new HashSet<string>(); | |
if (WarningsAsErrors != null) | |
{ | |
allWarningsAsErrors.UnionWith(WarningsAsErrors); | |
} | |
if (_warningsAsErrorsByProject != null) | |
{ | |
if (_warningsAsErrorsByProject.TryGetValue(key, out ISet<string> warningsAsErrors)) | |
{ | |
allWarningsAsErrors.UnionWith(warningsAsErrors); | |
} | |
} | |
return allWarningsAsErrors; | |
ISet<string> warningsAsErrors = null; | |
if (_warningsAsErrorsByProject is not null && _warningsAsErrorsByProject.TryGetValue(key, out warningsAsErrors) | |
{ | |
if (WarningsAsErrors is not null) | |
{ | |
warningsAsErrors.UnionWith(WarningsAsErrors); | |
} | |
return warningsAsErrors; | |
} | |
else | |
{ | |
return WarningsAsErrors; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If they're both null they'll return through the first if statement. I do like your suggested change though, less to sift through.
[Fact] | ||
public void TaskReturnsHasLoggedErrorAndLogsWarningAsError_WarningIsAlsoMessage_BuildShouldContinue() | ||
{ | ||
using (TestEnvironment env = TestEnvironment.Create(_output)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't normally look critically at tests, but please make sure your tests are substantially different from each other. I diffed your first and third tests with the second one, and they differed by one and two lines respectively. That could trivially become a theory and cut out almost a hundred lines of code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point. Streamlined the tests, also realized the two batched builds test were covering the same scenario. Removed one of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added a few very minor comments, totally optional.
|
||
Assert.NotNull(deserializedConfig.WarningsAsMessages); | ||
config.WarningsAsMessages.SequenceEqual(deserializedConfig.WarningsAsMessages, StringComparer.Ordinal).ShouldBeTrue(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
super-nit: Extra empty line.
public bool ShouldTreatWarningAsError(string warningCode) | ||
{ | ||
if (WarningsAsErrors == null) | ||
// Warnings as messages overrides warnings as errors. | ||
if (WarningsAsErrors == null || (WarningsAsMessages != null && WarningsAsMessages.Contains(warningCode))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
super-nit:
if (WarningsAsErrors == null || (WarningsAsMessages != null && WarningsAsMessages.Contains(warningCode))) | |
if (WarningsAsErrors == null || WarningsAsMessages?.Contains(warningCode) == true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, does empty WarningsAsMessages
mean that all warnings should be treated as messages or does only WarningsAsErrors
have this behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great question, I had the same when doing this PR. It doesn't look like it. ShouldTreatWarningAsError
has this check:
if (WarningsAsErrors != null)
{
// Global warnings as errors apply to all projects. If the list is empty or contains the code, the warning should be treated as an error
//
if (WarningsAsErrors.Count == 0 || WarningsAsErrors.Contains(warningEvent.Code))
{
return true;
}
}
whereas ShouldTreatWarningAsMessage
checks like so:
// This only applies if the user specified /nowarn at the command-line or added the warning code through the object model
//
if (WarningsAsMessages?.Contains(warningEvent.Code) == true)
{
return true;
}
There's no check for the count being zero, so I don't think that rule applies.
src/MSBuild/OutOfProcTaskHostNode.cs
Outdated
public bool ShouldTreatWarningAsError(string warningCode) | ||
{ | ||
return WarningsAsErrors != null && (WarningsAsErrors.Count == 0 || WarningsAsErrors.Contains(warningCode)); | ||
// Warnings as messages overrides warnings as errors. | ||
if (WarningsAsErrors == null || (WarningsAsMessages != null && WarningsAsMessages.Contains(warningCode))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (WarningsAsErrors == null || (WarningsAsMessages != null && WarningsAsMessages.Contains(warningCode))) | |
if (WarningsAsErrors == null || WarningsAsMessages?.Contains(warningCode) == true) |
src/Shared/TaskHostConfiguration.cs
Outdated
translator.Translate(collection: ref _warningsAsMessages, | ||
objectTranslator: (ITranslator t, ref string s) => t.Translate(ref s), | ||
#if CLR2COMPATIBILITY | ||
collectionFactory: count => new HashSet<string>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that this is the same as _warningsAsErrors
a few lines above but I'm wondering if we shouldn't be constructing with StringComparer.OrdinalIgnoreCase
on CLR2 as well. The constructor taking both capacity and comparer was introduced later but the one taking IEqualityComparer<T>
should be available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Thanks for refactoring.
/// </returns> | ||
public ICollection<string> GetWarningsToBeLoggedAsErrorsByProject(BuildEventContext context) | ||
public ICollection<string> GetWarningsAsErrors(BuildEventContext context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd still prefer for this method and the next to just call HelperMethodName(context, _warningsAsErrorsByProject, WarningsAsErrors) or the equivalent and have HelperMethodName include the real logic, but that's a little nit.
Not sure what this test case is and why it's failing: |
Fixes #6306
Context
#6174 didn't properly account for warningsaserrors and warningsasmessages together. This PR makes each taskhost aware of both WarningsAsMessages and WarningsAsErrors when telling tasks whether or not an error should be treated as a warning.
Changes Made
LoggingService and TaskLoggingContext expose a
GetWarningsAsMessages
to each taskhost for them to store.No changes to IBE8 API
Taskhosts first check if a warning would be treated as a message when telling tasks whether or not a warning should be treated as a warning.
Testing
Added test cases for WarningsAsMessages getting translated properly for the OOPTHN.
Added test cases for WarningsAsErrors and WarningsAsMessages for the same warning code.
Added test case for MSBuildTreatWarningsAsErrors and WarningsAsMessages on a logged warning (WAM takes priority)
Notes
Does
MSBuildTreatWarningsAsMessages
exist as a property a user can set?If
WarningsAsMessages
is an empty set, does it follow the rules ofWarningsAsErrors
? (empty set = all warnings as errors) /cc: @jeffkl