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 using System;
5 using System.IO;
6 using System.Xml;
7 using System.Resources;
8 using System.Globalization;
9 using System.Text;
10 using Microsoft.Build.BuildEngine;
11 using NUnit.Framework;
12 using System.Collections;
13 using System.Collections.Generic;
14 using Microsoft.Build.Framework;
15 
16 
17 namespace Microsoft.Build.UnitTests
18 {
19     /*
20      * Class:   MockLogger
21      * Owner:   jomof
22      *
23      * Mock logger class. Keeps track of errors and warnings and also builds
24      * up a raw string (fullLog) that contains all messages, warnings, errors.
25      *
26      */
27     internal sealed class MockLogger : ILogger
28     {
29 #region Properties
30         private int errorCount = 0;
31         private int warningCount = 0;
32         private StringBuilder fullLog = new StringBuilder();
33         private List<BuildErrorEventArgs> errors = new List<BuildErrorEventArgs>();
34         private List<BuildWarningEventArgs> warnings = new List<BuildWarningEventArgs>();
35         private List<ExternalProjectStartedEventArgs> externalProjectStartedEvents = new List<ExternalProjectStartedEventArgs>();
36         private List<ExternalProjectFinishedEventArgs> externalProjectFinishedEvents = new List<ExternalProjectFinishedEventArgs>();
37 
38         /*
39          * Method:  ErrorCount
40          * Owner:   jomof
41          *
42          * The count of all errors seen so far.
43          *
44          */
45         internal int ErrorCount
46         {
47             get { return this.errorCount; }
48         }
49 
50         /*
51          * Method:  WarningCount
52          * Owner:   jomof
53          *
54          * The count of all warnings seen so far.
55          *
56          */
57         internal int WarningCount
58         {
59             get { return this.warningCount; }
60         }
61 
62         /// <summary>
63         /// Return the list of logged errors
64         /// </summary>
65         /// <owner>LukaszG</owner>
66         internal List<BuildErrorEventArgs> Errors
67         {
68             get
69             {
70                 return this.errors;
71             }
72         }
73 
74         /// <summary>
75         /// Returns the list of logged warnings
76         /// </summary>
77         /// <owner>LukaszG</owner>
78         internal List<BuildWarningEventArgs> Warnings
79         {
80             get
81             {
82                 return this.warnings;
83             }
84         }
85 
86         /// <summary>
87         /// List of ExternalProjectStarted events
88         /// </summary>
89         internal List<ExternalProjectStartedEventArgs> ExternalProjectStartedEvents
90         {
91             get { return this.externalProjectStartedEvents; }
92         }
93 
94         /// <summary>
95         /// List of ExternalProjectFinished events
96         /// </summary>
97         internal List<ExternalProjectFinishedEventArgs> ExternalProjectFinishedEvents
98         {
99             get { return this.externalProjectFinishedEvents; }
100         }
101 
102         /*
103          * Method:  FullLog
104          * Owner:   jomof
105          *
106          * The raw concatenation of all messages, errors and warnings seen so far.
107          *
108          */
109         internal string FullLog
110         {
111             get { return this.fullLog.ToString(); }
112         }
113 #endregion
114 
115 #region Minimal ILogger implementation
116 
117         /*
118          * Property:    Verbosity
119          * Owner:       SumedhK
120          *
121          * The level of detail to show in the event log.
122          *
123          */
124         public LoggerVerbosity Verbosity
125         {
126             get  {return LoggerVerbosity.Normal;}
127             set  {/* do nothing */}
128         }
129 
130         /*
131          * Property:    Parameters
132          * Owner:       SumedhK
133          *
134          * The mock logger does not take parameters.
135          *
136          */
137         public string Parameters
138         {
139             get
140             {
141                 return null;
142             }
143 
144             set
145             {
146                 // do nothing
147             }
148         }
149 
150         /*
151          * Method:  Initialize
152          * Owner:   jomof
153          *
154          * Add a new build event.
155          *
156          */
Initialize(IEventSource eventSource)157         public void Initialize(IEventSource eventSource)
158         {
159             eventSource.AnyEventRaised +=
160                     new AnyEventHandler(LoggerEventHandler);
161         }
162 
163         /// <summary>
164         /// Clears the content of the log "file"
165         /// </summary>
ClearLog()166         public void ClearLog()
167         {
168             this.fullLog = new StringBuilder();
169         }
170 
171         /*
172          * Method:  Shutdown
173          * Owner:   SumedhK
174          *
175          * The mock logger does not need to release any resources.
176          *
177          */
Shutdown()178         public void Shutdown()
179         {
180             // do nothing
181         }
182 #endregion
183 
184         /*
185          * Method:  LoggerEventHandler
186          * Owner:   jomof
187          *
188          * Recieves build events and logs them the way we like.
189          *
190          */
LoggerEventHandler(object sender, BuildEventArgs eventArgs)191         internal void LoggerEventHandler(object sender, BuildEventArgs eventArgs)
192         {
193             if (eventArgs is BuildWarningEventArgs)
194             {
195                 BuildWarningEventArgs w = (BuildWarningEventArgs) eventArgs;
196 
197                 // hack: disregard the MTA warning.
198                 // need the second condition to pass on ploc builds
199                 if (w.Code != "MSB4056" && !w.Message.Contains("MSB4056"))
200                 {
201                     fullLog.AppendFormat("{0}({1},{2}): {3} warning {4}: {5}\r\n",
202                         w.File,
203                         w.LineNumber,
204                         w.ColumnNumber,
205                         w.Subcategory,
206                         w.Code,
207                         w.Message);
208 
209                     ++warningCount;
210                     this.warnings.Add(w);
211                 }
212             }
213             else if (eventArgs is BuildErrorEventArgs)
214             {
215                 BuildErrorEventArgs e = (BuildErrorEventArgs) eventArgs;
216 
217                 fullLog.AppendFormat("{0}({1},{2}): {3} error {4}: {5}\r\n",
218                     e.File,
219                     e.LineNumber,
220                     e.ColumnNumber,
221                     e.Subcategory,
222                     e.Code,
223                     e.Message);
224 
225                 ++errorCount;
226                 this.errors.Add(e);
227             }
228             else
229             {
230                 fullLog.Append(eventArgs.Message);
231                 fullLog.Append("\r\n");
232             }
233 
234             if (eventArgs is ExternalProjectStartedEventArgs)
235             {
236                 this.ExternalProjectStartedEvents.Add((ExternalProjectStartedEventArgs)eventArgs);
237             }
238             else if (eventArgs is ExternalProjectFinishedEventArgs)
239             {
240                 this.ExternalProjectFinishedEvents.Add((ExternalProjectFinishedEventArgs)eventArgs);
241             }
242 
243             if (eventArgs is BuildFinishedEventArgs)
244             {
245                 // Console.Write in the context of a unit test is very expensive.  A hundred
246                 // calls to Console.Write can easily take two seconds on a fast machine.  Therefore, only
247                 // do the Console.Write once at the end of the build.
248                 Console.Write(FullLog);
249             }
250         }
251 
252         // Lazy-init property returning the MSBuild engine resource manager
253         static private ResourceManager EngineResourceManager
254         {
255             get
256             {
257                 if (engineResourceManager == null)
258                 {
259                     engineResourceManager = new ResourceManager("Microsoft.Build.Engine.Resources.Strings", typeof(Engine).Assembly);
260                 }
261 
262                 return engineResourceManager;
263             }
264         }
265 
266         static private ResourceManager engineResourceManager = null;
267 
268         // Gets the resource string given the resource ID
GetString(string stringId)269         static public string GetString(string stringId)
270         {
271             return EngineResourceManager.GetString(stringId, CultureInfo.CurrentUICulture);
272         }
273 
274         /// <summary>
275         /// Assert that the log file contains the given strings, in order.
276         /// </summary>
277         /// <param name="contains"></param>
AssertLogContains(params string[] contains)278         internal void AssertLogContains(params string[] contains)
279         {
280             StringReader reader = new StringReader(FullLog);
281             int index = 0;
282             string currentLine = reader.ReadLine();
283             while(currentLine != null)
284             {
285                 if (currentLine.Contains(contains[index]))
286                 {
287                     index++;
288                     if (index == contains.Length) break;
289                 }
290                 currentLine = reader.ReadLine();
291             }
292             if (index != contains.Length)
293             {
294                 Assertion.Fail(String.Format(CultureInfo.CurrentCulture, "Log was expected to contain '{0}', but did not.\n=======\n" + FullLog + "\n=======", contains[index]));
295             }
296         }
297 
298         /// <summary>
299         /// Assert that the log file contains the given string.
300         /// </summary>
301         /// <param name="contains"></param>
AssertLogDoesntContain(string contains)302         internal void AssertLogDoesntContain(string contains)
303         {
304             Assertion.Assert(
305                 String.Format("Log was not expected to contain '{0}', but did.", contains),
306                 !FullLog.Contains(contains));
307         }
308     }
309 }
310