Skip to content

Commit 6c6609b

Browse files
GeertvanHorrikdevlead
authored andcommitted
(GH-4471) Improve script cache hash generation algorithm
1 parent d6c0cc3 commit 6c6609b

File tree

2 files changed

+127
-1
lines changed

2 files changed

+127
-1
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Cake.Core.Configuration;
4+
using Cake.Core.Diagnostics;
5+
using Cake.Core.Reflection;
6+
using Cake.Core.Scripting;
7+
using Cake.Infrastructure;
8+
using Cake.Infrastructure.Scripting;
9+
using NSubstitute;
10+
using Xunit;
11+
12+
namespace Cake.Tests.Infrastructure.Scripting
13+
{
14+
public sealed partial class RoslynScriptSessionTests
15+
{
16+
[Fact]
17+
public void Generates_Equal_Hash_For_Different_Directories_Equal_Files()
18+
{
19+
var hashes = new HashSet<string>();
20+
var directories = new[]
21+
{
22+
"c:/source/test1",
23+
"c:/source/test2",
24+
"c:/source/test3"
25+
};
26+
27+
foreach (var directory in directories)
28+
{
29+
var scriptHost = Substitute.For<IScriptHost>();
30+
scriptHost.Context.Environment.WorkingDirectory.Returns(directory);
31+
32+
var assemblyLoader = Substitute.For<IAssemblyLoader>();
33+
var cakeConfiguration = Substitute.For<ICakeConfiguration>();
34+
var cakeLog = Substitute.For<ICakeLog>();
35+
36+
var scriptHostSettings = Substitute.For<IScriptHostSettings>();
37+
scriptHostSettings.Script.Returns(new Core.IO.FilePath($"{directory}/build.cake"));
38+
39+
var scriptEngine = new RoslynScriptSession(scriptHost, assemblyLoader,
40+
cakeConfiguration, cakeLog, scriptHostSettings);
41+
42+
var script = GenerateScript(directory);
43+
44+
var hash = scriptEngine.GetScriptHash(script);
45+
hashes.Add(hash);
46+
}
47+
48+
Assert.Single(hashes);
49+
}
50+
51+
[Fact]
52+
public void Generates_Different_Hash_For_Equal_Directories_Different_Files()
53+
{
54+
var hashes = new HashSet<string>();
55+
56+
var directory = "c:/source/test1";
57+
58+
var randomLines = new[]
59+
{
60+
"random 1",
61+
"random 2",
62+
"random 3"
63+
};
64+
65+
foreach (var randomLine in randomLines)
66+
{
67+
var scriptHost = Substitute.For<IScriptHost>();
68+
scriptHost.Context.Environment.WorkingDirectory.Returns(directory);
69+
70+
var assemblyLoader = Substitute.For<IAssemblyLoader>();
71+
var cakeConfiguration = Substitute.For<ICakeConfiguration>();
72+
var cakeLog = Substitute.For<ICakeLog>();
73+
74+
var scriptHostSettings = Substitute.For<IScriptHostSettings>();
75+
scriptHostSettings.Script.Returns(new Core.IO.FilePath($"{directory}/build.cake"));
76+
77+
var scriptEngine = new RoslynScriptSession(scriptHost, assemblyLoader,
78+
cakeConfiguration, cakeLog, scriptHostSettings);
79+
80+
var script = GenerateScript(directory, new[]
81+
{
82+
randomLine
83+
});
84+
85+
var hash = scriptEngine.GetScriptHash(script);
86+
hashes.Add(hash);
87+
}
88+
89+
Assert.Equal(randomLines.Length, hashes.Count);
90+
}
91+
92+
private Script GenerateScript(string path, IReadOnlyList<string> additionalLines = null)
93+
{
94+
var lines = new List<string>
95+
{
96+
$"#line 1 \"{path}/build.cake\"",
97+
"var x = 1;",
98+
"var y = 2;",
99+
"var z = x + y;"
100+
};
101+
102+
if (additionalLines is not null)
103+
{
104+
lines.AddRange(additionalLines);
105+
}
106+
107+
var script = new Script(Array.Empty<string>(), lines,
108+
Array.Empty<ScriptAlias>(), Array.Empty<string>(), Array.Empty<string>(),
109+
Array.Empty<string>());
110+
111+
return script;
112+
}
113+
}
114+
}

src/Cake/Infrastructure/Scripting/RoslynScriptSession.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,21 @@ private FilePath GetCachedAssemblyPath(Script script, FilePath scriptName)
213213
'.',
214214
scriptName.GetFilenameWithoutExtension().FullPath,
215215
_host.GetType().Name,
216-
FastHash.GenerateHash(Encoding.UTF8.GetBytes(string.Concat(script.Lines))),
216+
GetScriptHash(script),
217217
"dll"));
218218

219+
public string GetScriptHash(Script script)
220+
{
221+
// Remove specific lines that could cause the same files to generate different
222+
// hashes. See https://github.com/cake-build/cake/issues/4471 for more information
223+
var linesToHash = script.Lines
224+
.Where(line => !line.StartsWith("#line ", StringComparison.OrdinalIgnoreCase))
225+
.ToArray();
226+
227+
var hash = FastHash.GenerateHash(Encoding.UTF8.GetBytes(string.Concat(linesToHash)));
228+
return hash;
229+
}
230+
219231
private void RunScriptAssembly(string assemblyPath)
220232
{
221233
var assembly = _loader.Load(assemblyPath, false);

0 commit comments

Comments
 (0)