1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 
4 #pragma warning disable 436
5 
6 using System;
7 using System.IO;
8 using System.Collections.Generic;
9 using System.Text;
10 
11 using Microsoft.Build.Engine.UnitTests;
12 using Microsoft.Build.Framework;
13 using Microsoft.Build.Logging;
14 using Microsoft.Build.Shared;
15 
16 
17 
18 using EventSourceSink = Microsoft.Build.BackEnd.Logging.EventSourceSink;
19 using Project = Microsoft.Build.Evaluation.Project;
20 using Xunit;
21 
22 namespace Microsoft.Build.UnitTests
23 {
24     public class FileLogger_Tests
25     {
26         /// <summary>
27         /// Basic test of the file logger.  Writes to a log file in the temp directory.
28         /// </summary>
29         [Fact]
Basic()30         public void Basic()
31         {
32             FileLogger fileLogger = new FileLogger();
33             string logFile = FileUtilities.GetTemporaryFile();
34             fileLogger.Parameters = "verbosity=Normal;logfile=" + logFile;
35 
36             Project project = ObjectModelHelpers.CreateInMemoryProject(@"
37                 <Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
38                     <Target Name=`Build`>
39                         <Message Text=`Hello world from the FileLogger`/>
40                     </Target>
41                 </Project>
42                 ");
43 
44             project.Build(fileLogger);
45 
46             project.ProjectCollection.UnregisterAllLoggers();
47 
48             string log = File.ReadAllText(logFile);
49             Assert.True(log.Contains("Hello world from the FileLogger")); // "Log should have contained message"
50 
51             File.Delete(logFile);
52         }
53 
54         /// <summary>
55         /// Basic case of logging a message to a file
56         /// Verify it logs and encoding is ANSI
57         /// </summary>
58         [Fact]
BasicNoExistingFile()59         public void BasicNoExistingFile()
60         {
61             string log = null;
62 
63             try
64             {
65                 log = GetTempFilename();
66                 SetUpFileLoggerAndLogMessage("logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
67                 VerifyFileContent(log, "message here");
68 
69 
70                 byte[] content = ReadRawBytes(log);
71                 Assert.Equal((byte)109, content[0]); // 'm'
72             }
73             finally
74             {
75                 if (null != log) File.Delete(log);
76             }
77         }
78 
79         /// <summary>
80         /// Invalid file should error nicely
81         /// </summary>
82         [Fact]
83         [Trait("Category", "netcore-osx-failing")]
84         [Trait("Category", "netcore-linux-failing")]
85         [Trait("Category", "mono-osx-failing")]
InvalidFile()86         public void InvalidFile()
87         {
88             Assert.Throws<LoggerException>(() =>
89             {
90                 string log = null;
91 
92                 try
93                 {
94                     SetUpFileLoggerAndLogMessage("logfile=||invalid||", new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
95                 }
96                 finally
97                 {
98                     if (null != log) File.Delete(log);
99                 }
100             }
101            );
102         }
103         /// <summary>
104         /// Specific verbosity overrides global verbosity
105         /// </summary>
106         [Fact]
SpecificVerbosity()107         public void SpecificVerbosity()
108         {
109             string log = null;
110 
111             try
112             {
113                 log = GetTempFilename();
114                 FileLogger fl = new FileLogger();
115                 EventSourceSink es = new EventSourceSink();
116                 fl.Parameters = "verbosity=diagnostic;logfile=" + log;  // diagnostic specific setting
117                 fl.Verbosity = LoggerVerbosity.Quiet; // quiet global setting
118                 fl.Initialize(es);
119                 fl.MessageHandler(null, new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
120                 fl.Shutdown();
121 
122                 // expect message to appear because diagnostic not quiet verbosity was used
123                 VerifyFileContent(log, "message here");
124             }
125             finally
126             {
127                 if (null != log) File.Delete(log);
128             }
129         }
130 
131         /// <summary>
132         /// Test the short hand verbosity settings for the file logger
133         /// </summary>
134         [Fact]
ValidVerbosities()135         public void ValidVerbosities()
136         {
137             string[] verbositySettings = new string[] { "Q", "quiet", "m", "minimal", "N", "normal", "d", "detailed", "diag", "DIAGNOSTIC" };
138             LoggerVerbosity[] verbosityEnumerations = new LoggerVerbosity[] {LoggerVerbosity.Quiet, LoggerVerbosity.Quiet,
139                                                                              LoggerVerbosity.Minimal, LoggerVerbosity.Minimal,
140                                                                              LoggerVerbosity.Normal, LoggerVerbosity.Normal,
141                                                                              LoggerVerbosity.Detailed, LoggerVerbosity.Detailed,
142                                                                              LoggerVerbosity.Diagnostic, LoggerVerbosity.Diagnostic};
143             for (int i = 0; i < verbositySettings.Length; i++)
144             {
145                 FileLogger fl = new FileLogger();
146                 fl.Parameters = "verbosity=" + verbositySettings[i] + ";";
147                 EventSourceSink es = new EventSourceSink();
148                 fl.Initialize(es);
149                 fl.Shutdown();
150                 Assert.Equal(fl.Verbosity, verbosityEnumerations[i]);
151             }
152 
153             // Do the same using the v shorthand
154             for (int i = 0; i < verbositySettings.Length; i++)
155             {
156                 FileLogger fl = new FileLogger();
157                 fl.Parameters = "v=" + verbositySettings[i] + ";";
158                 EventSourceSink es = new EventSourceSink();
159                 fl.Initialize(es);
160                 fl.Shutdown();
161                 Assert.Equal(fl.Verbosity, verbosityEnumerations[i]);
162             }
163         }
164 
165         /// <summary>
166         /// Invalid verbosity setting
167         /// </summary>
168         [Fact]
InvalidVerbosity()169         public void InvalidVerbosity()
170         {
171             Assert.Throws<LoggerException>(() =>
172             {
173                 FileLogger fl = new FileLogger();
174                 fl.Parameters = "verbosity=CookiesAndCream";
175                 EventSourceSink es = new EventSourceSink();
176                 fl.Initialize(es);
177             }
178            );
179         }
180         /// <summary>
181         /// Invalid encoding setting
182         /// </summary>
183         [Fact]
InvalidEncoding()184         public void InvalidEncoding()
185         {
186             Assert.Throws<LoggerException>(() =>
187             {
188                 string log = null;
189 
190                 try
191                 {
192                     log = GetTempFilename();
193                     FileLogger fl = new FileLogger();
194                     EventSourceSink es = new EventSourceSink();
195                     fl.Parameters = "encoding=foo;logfile=" + log;
196                     fl.Initialize(es);
197                 }
198                 finally
199                 {
200                     if (null != log) File.Delete(log);
201                 }
202             }
203            );
204         }
205 
206         /// <summary>
207         /// Valid encoding setting
208         /// </summary>
209         [Fact]
ValidEncoding()210         public void ValidEncoding()
211         {
212             string log = null;
213 
214             try
215             {
216                 log = GetTempFilename();
217                 SetUpFileLoggerAndLogMessage("encoding=utf-16;logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
218                 byte[] content = ReadRawBytes(log);
219 
220                 // FF FE is the BOM for UTF16
221                 Assert.Equal((byte)255, content[0]);
222                 Assert.Equal((byte)254, content[1]);
223             }
224             finally
225             {
226                 if (null != log) File.Delete(log);
227             }
228         }
229 
230         /// <summary>
231         /// Valid encoding setting
232         /// </summary>
233         [Fact]
ValidEncoding2()234         public void ValidEncoding2()
235         {
236             string log = null;
237 
238             try
239             {
240                 log = GetTempFilename();
241                 SetUpFileLoggerAndLogMessage("encoding=utf-8;logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
242                 byte[] content = ReadRawBytes(log);
243 
244                 // EF BB BF is the BOM for UTF8
245                 Assert.Equal((byte)239, content[0]);
246                 Assert.Equal((byte)187, content[1]);
247                 Assert.Equal((byte)191, content[2]);
248             }
249             finally
250             {
251                 if (null != log) File.Delete(log);
252             }
253         }
254 
255         /// <summary>
256         /// Read the raw byte content of a file
257         /// </summary>
258         /// <param name="log"></param>
259         /// <returns></returns>
ReadRawBytes(string log)260         private byte[] ReadRawBytes(string log)
261         {
262             byte[] content;
263             using (FileStream stream = new FileStream(log, FileMode.Open))
264             {
265                 content = new byte[stream.Length];
266 
267                 for (int i = 0; i < stream.Length; i++)
268                 {
269                     content[i] = (byte)stream.ReadByte();
270                 }
271             }
272 
273             return content;
274         }
275 
276         /// <summary>
277         /// Logging a message to a file that already exists should overwrite it
278         /// </summary>
279         [Fact]
BasicExistingFileNoAppend()280         public void BasicExistingFileNoAppend()
281         {
282             string log = null;
283 
284             try
285             {
286                 log = GetTempFilename();
287                 WriteContentToFile(log);
288                 SetUpFileLoggerAndLogMessage("logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
289                 VerifyFileContent(log, "message here");
290             }
291             finally
292             {
293                 if (null != log) File.Delete(log);
294             }
295         }
296 
297         /// <summary>
298         /// Logging to a file that already exists, with "append" set, should append
299         /// </summary>
300         [Fact]
BasicExistingFileAppend()301         public void BasicExistingFileAppend()
302         {
303             string log = null;
304 
305             try
306             {
307                 log = GetTempFilename();
308                 WriteContentToFile(log);
309                 SetUpFileLoggerAndLogMessage("append;logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
310                 VerifyFileContent(log, "existing content\nmessage here");
311             }
312             finally
313             {
314                 if (null != log) File.Delete(log);
315             }
316         }
317 
318         /// <summary>
319         /// Logging to a file in a directory that doesn't exists
320         /// </summary>
321         [Fact]
BasicNoExistingDirectory()322         public void BasicNoExistingDirectory()
323         {
324             string directory = Path.Combine(ObjectModelHelpers.TempProjectDir, Guid.NewGuid().ToString("N"));
325             string log = Path.Combine(directory, "build.log");
326             Assert.False(Directory.Exists(directory));
327             Assert.False(File.Exists(log));
328 
329             try
330             {
331                 SetUpFileLoggerAndLogMessage("logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
332                 VerifyFileContent(log, "message here");
333             }
334             finally
335             {
336                 ObjectModelHelpers.DeleteDirectory(directory);
337             }
338         }
339 
340         [Theory]
341         [InlineData("warningsonly")]
342         [InlineData("errorsonly")]
343         [InlineData("errorsonly;warningsonly")]
EmptyErrorLogUsingWarningsErrorsOnly(string loggerOption)344         public void EmptyErrorLogUsingWarningsErrorsOnly(string loggerOption)
345         {
346             using (var env = TestEnvironment.Create())
347             {
348                 var logFile = env.CreateFile(".log").Path;
349 
350                 // Note: Only the ParallelConsoleLogger supports this scenario (log file empty on no error/warn). We
351                 // need to explicitly enable it here with the 'ENABLEMPLOGGING' flag.
352                 FileLogger fileLogger = new FileLogger {Parameters = $"{loggerOption};logfile={logFile};ENABLEMPLOGGING" };
353 
354                 Project project = ObjectModelHelpers.CreateInMemoryProject(@"
355                 <Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
356                     <Target Name=`Build`>
357                         <Message Text=`Hello world from the FileLogger`/>
358                     </Target>
359                 </Project>");
360 
361                 project.Build(fileLogger);
362                 project.ProjectCollection.UnregisterAllLoggers();
363 
364                 // File should exist and be 0 length (no summary information, etc.)
365                 var result = new FileInfo(logFile);
366                 Assert.True(result.Exists);
367                 Assert.Equal(0, new FileInfo(logFile).Length);
368             }
369         }
370 
371         /// <summary>
372         /// Gets a filename for a nonexistent temporary file.
373         /// </summary>
374         /// <returns></returns>
GetTempFilename()375         private string GetTempFilename()
376         {
377             string path = FileUtilities.GetTemporaryFile();
378             File.Delete(path);
379             return path;
380         }
381 
382         /// <summary>
383         /// Writes a string to a file.
384         /// </summary>
385         /// <param name="log"></param>
WriteContentToFile(string log)386         private void WriteContentToFile(string log)
387         {
388             using (StreamWriter sw = FileUtilities.OpenWrite(log, false))
389             {
390                 sw.WriteLine("existing content");
391             }
392         }
393 
394         /// <summary>
395         /// Creates a FileLogger, sets its parameters and initializes it,
396         /// logs a message to it, and calls shutdown
397         /// </summary>
398         /// <param name="parameters"></param>
399         /// <returns></returns>
SetUpFileLoggerAndLogMessage(string parameters, BuildMessageEventArgs message)400         private void SetUpFileLoggerAndLogMessage(string parameters, BuildMessageEventArgs message)
401         {
402             FileLogger fl = new FileLogger();
403             EventSourceSink es = new EventSourceSink();
404             fl.Parameters = parameters;
405             fl.Initialize(es);
406             fl.MessageHandler(null, message);
407             fl.Shutdown();
408             return;
409         }
410 
411         /// <summary>
412         /// Verifies that a file contains exactly the expected content.
413         /// </summary>
414         /// <param name="file"></param>
415         /// <param name="expectedContent"></param>
VerifyFileContent(string file, string expectedContent)416         private void VerifyFileContent(string file, string expectedContent)
417         {
418             string actualContent;
419             using (StreamReader sr = FileUtilities.OpenRead(file))
420             {
421                 actualContent = sr.ReadToEnd();
422             }
423 
424             string[] actualLines = actualContent.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
425             string[] expectedLines = expectedContent.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
426 
427             Assert.Equal(expectedLines.Length, actualLines.Length);
428 
429             for (int i = 0; i < expectedLines.Length; i++)
430             {
431                 Assert.Equal(expectedLines[i].Trim(), actualLines[i].Trim());
432             }
433         }
434 
435         #region DistributedLogger
436         /// <summary>
437         /// Check the ability of the distributed logger to correctly tell its internal file logger where to log the file
438         /// </summary>
439         [Fact]
DistributedFileLoggerParameters()440         public void DistributedFileLoggerParameters()
441         {
442             DistributedFileLogger fileLogger = new DistributedFileLogger();
443             try
444             {
445                 fileLogger.NodeId = 0;
446                 fileLogger.Initialize(new EventSourceSink());
447                 Assert.Equal(0, string.Compare(fileLogger.InternalFilelogger.Parameters, "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=msbuild0.log;", StringComparison.OrdinalIgnoreCase));
448                 fileLogger.Shutdown();
449 
450                 fileLogger.NodeId = 3;
451                 fileLogger.Parameters = "logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "mylogfile.log");
452                 fileLogger.Initialize(new EventSourceSink());
453                 Assert.Equal(0, string.Compare(fileLogger.InternalFilelogger.Parameters, "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "mylogfile3.log") + ";", StringComparison.OrdinalIgnoreCase));
454                 fileLogger.Shutdown();
455 
456                 fileLogger.NodeId = 4;
457                 fileLogger.Parameters = "logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "mylogfile.log");
458                 fileLogger.Initialize(new EventSourceSink());
459                 Assert.Equal(0, string.Compare(fileLogger.InternalFilelogger.Parameters, "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "mylogfile4.log") + ";", StringComparison.OrdinalIgnoreCase));
460                 fileLogger.Shutdown();
461 
462                 Directory.CreateDirectory(Path.Combine(Directory.GetCurrentDirectory(), "tempura"));
463                 fileLogger.NodeId = 1;
464                 fileLogger.Parameters = "logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "tempura", "mylogfile.log");
465                 fileLogger.Initialize(new EventSourceSink());
466                 Assert.Equal(0, string.Compare(fileLogger.InternalFilelogger.Parameters, "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "tempura", "mylogfile1.log") + ";", StringComparison.OrdinalIgnoreCase));
467                 fileLogger.Shutdown();
468             }
469             finally
470             {
471                 if (Directory.Exists(Path.Combine(Directory.GetCurrentDirectory(), "tempura")))
472                 {
473                     File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "tempura", "mylogfile1.log"));
474                     FileUtilities.DeleteWithoutTrailingBackslash(Path.Combine(Directory.GetCurrentDirectory(), "tempura"));
475                 }
476                 File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "mylogfile0.log"));
477                 File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "mylogfile3.log"));
478                 File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "mylogfile4.log"));
479             }
480         }
481 
482         [Fact]
DistributedLoggerNullEmpty()483         public void DistributedLoggerNullEmpty()
484         {
485             Assert.Throws<LoggerException>(() =>
486             {
487                 DistributedFileLogger fileLogger = new DistributedFileLogger();
488                 fileLogger.NodeId = 0;
489                 fileLogger.Initialize(new EventSourceSink());
490 
491                 fileLogger.NodeId = 1;
492                 fileLogger.Parameters = "logfile=";
493                 fileLogger.Initialize(new EventSourceSink());
494                 Assert.True(false);
495             }
496            );
497         }
498         #endregion
499 
500     }
501 }
502