11using System ;
2+ using System . Collections . Concurrent ;
23using System . Collections . Generic ;
34using System . ComponentModel ;
45using System . Diagnostics ;
56using System . IO ;
67using System . Linq ;
8+ using System . Runtime . InteropServices ;
79using System . Text . RegularExpressions ;
10+ using System . Threading . Tasks ;
811
912namespace CheckTestOutput
1013{
@@ -42,7 +45,7 @@ public OutputChecker(
4245 this . CheckDirectory = Path . Combine ( Path . GetDirectoryName ( calledFrom ) , directory ) ;
4346 }
4447
45- DoesGitWork = new Lazy < bool > ( ( ) => {
48+ DoesGitWork = doesGitWorkCache . GetOrAdd ( directory , new Lazy < bool > ( ( ) => {
4649 try
4750 {
4851 var path = RunGitCommand ( "rev-parse" , "--show-toplevel" ) ;
@@ -64,10 +67,12 @@ public OutputChecker(
6467 Console . WriteLine ( "Error: " + e ) ;
6568 return false ;
6669 }
67- } ) ;
70+ } ) ) ;
6871
6972 }
7073
74+ private static ConcurrentDictionary < string , Lazy < bool > > doesGitWorkCache = new ( ) ;
75+
7176 public string CheckDirectory { get ; }
7277 private string [ ] _nonDeterminismSanitizers ;
7378 /// <summary> List of regular expressions that are replaced by a sequential id for the purpose of the check. The sanitization preserves equality over the checked string (equal strings are replaced by equal id, different strings by different ids) </summary>
@@ -80,31 +85,51 @@ public OutputChecker(
8085
8186 private string [ ] RunGitCommand ( params string [ ] args )
8287 {
88+ #if DEBUG
89+ Console . WriteLine ( "Running git command: " + string . Join ( " " , args ) ) ;
90+ #endif
8391 // run `git ...args` in CheckDirectory working directory with 3 second timeout
8492 var procInfo = new ProcessStartInfo ( "git" )
8593 {
8694 WorkingDirectory = CheckDirectory ,
8795 UseShellExecute = false ,
8896 RedirectStandardOutput = true ,
8997 RedirectStandardError = true ,
98+ RedirectStandardInput = true ,
9099 CreateNoWindow = true ,
91100 StandardOutputEncoding = System . Text . Encoding . UTF8 ,
92101 StandardErrorEncoding = System . Text . Encoding . UTF8 ,
93102 } ;
94103 foreach ( var a in args )
95104 procInfo . ArgumentList . Add ( a ) ;
96105
106+
97107 var proc = Process . Start ( procInfo ) ;
98- if ( ! proc . WaitForExit ( 3000 ) )
108+
109+ var outputLines = new List < string > ( ) ;
110+ var outputReaderTask = Task . Run ( ( ) => {
111+ string line = null ;
112+ while ( ( line = proc . StandardOutput . ReadLine ( ) ) != null )
113+ {
114+ if ( line . Length > 0 )
115+ outputLines . Add ( line ) ;
116+ }
117+ } ) ;
118+
119+ // Literally, a Raspberry PI with a shitty SD card has faster IO than Azure Windows VM
120+ var timeout = RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ? 15_000 : 3_000 ;
121+ if ( ! proc . WaitForExit ( timeout ) )
99122 {
100123 proc . Kill ( ) ;
101- throw new Exception ( "Git command timed out") ;
124+ throw new Exception ( $ "`git { string . Join ( " " , args ) } ` command timed out") ;
102125 }
103126
104127 if ( proc . ExitCode != 0 )
105- throw new Exception ( "Git command failed: " + proc . StandardError . ReadToEnd ( ) ) ;
106-
107- return proc . StandardOutput . ReadToEnd ( ) . Split ( new [ ] { Environment . NewLine } , StringSplitOptions . RemoveEmptyEntries ) ;
128+ throw new Exception ( $ "`git { string . Join ( " " , args ) } ` command failed: " + proc . StandardError . ReadToEnd ( ) ) ;
129+
130+ outputReaderTask . Wait ( ) ;
131+
132+ return outputLines . ToArray ( ) ;
108133 }
109134
110135 static string [ ] ReadAllLines ( StreamReader reader )
@@ -143,6 +168,13 @@ private bool IsModified(string file)
143168 return ! gitOut . All ( string . IsNullOrEmpty ) ;
144169 }
145170
171+ private bool IsNewFile ( string file )
172+ {
173+ var gitOut = RunGitCommand ( "ls-files" , "--other" , file ) ;
174+ // if it outputs back the filename, it is other (untracked)
175+ return ! gitOut . All ( string . IsNullOrEmpty ) ;
176+ }
177+
146178 /// <summary> Applies the <see cref="NonDeterminismSanitizers" /> to the string. </summary>
147179 public string SanitizeString ( string outputString )
148180 {
@@ -163,21 +195,22 @@ public string SanitizeString(string outputString)
163195
164196 internal void CheckOutputCore ( string outputString , string checkName , string method , string fileExtension = "txt" )
165197 {
198+ outputString = outputString . Replace ( "\r \n " , "\n " ) . TrimEnd ( '\n ' ) ;
166199 outputString = SanitizeString ( outputString ) ;
167200
168201 Directory . CreateDirectory ( CheckDirectory ) ;
169202
170203 var filename = Path . Combine ( CheckDirectory , ( checkName == null ? method : $ "{ method } -{ checkName } ") + "." + fileExtension ) ;
171204
172-
173- if ( GetOldContent ( filename ) == outputString . Replace ( "\r " , "" ) )
205+ if ( GetOldContent ( filename ) == outputString )
174206 {
175207 // fine! Just check that the file is not changed - if it is changed or deleted, we rewrite
176208 if ( IsModified ( filename ) )
177209 {
178210 using ( var t = File . CreateText ( filename ) )
179211 {
180- t . WriteLine ( outputString ) ;
212+ t . Write ( outputString ) ;
213+ t . Write ( "\n " ) ;
181214 }
182215 }
183216 return ;
@@ -187,14 +220,25 @@ internal void CheckOutputCore(string outputString, string checkName, string meth
187220 {
188221 using ( var t = File . CreateText ( filename ) )
189222 {
190- t . WriteLine ( outputString ) ;
223+ t . Write ( outputString ) ;
224+ t . Write ( "\n " ) ;
191225 }
192226
193227 if ( IsModified ( filename ) )
194228 {
229+ if ( IsNewFile ( filename ) )
230+ {
231+ throw new Exception ( $ "{ Path . GetFileName ( filename ) } is not explicitly accepted - the file is untracked in git. To let this test pass, view the file and stage it. Confused? See https://github.com/exyi/CheckTestOutput/blob/master/trouble.md#untracked-file\n ") ;
232+ }
233+
234+
195235 var diff = RunGitCommand ( "diff" , filename ) ;
196236 if ( diff . All ( string . IsNullOrEmpty ) )
197- throw new Exception ( $ "{ Path . GetFileName ( filename ) } is not explicitly accepted - the file is untracked in git. To let this test pass, view the file and stage it. Confused? See https://github.com/exyi/CheckTestOutput/blob/master/trouble.md#untracked-file\n ") ;
237+ {
238+ // I guess fine from our perspective, but it's weird...
239+ Console . WriteLine ( $ "CheckTestOutput warning: { Path . GetFileName ( filename ) } is modified, but the diff is empty.") ;
240+ return ;
241+ }
198242 throw new Exception (
199243 $ "{ Path . GetFileName ( filename ) } has changed, the actual output differs from the previous accepted output:\n \n " +
200244 string . Join ( "\n " , diff ) + "\n \n " +
0 commit comments