Skip to content

Commit ce56906

Browse files
committed
Move file cache hashing into separate class and fix unit tests
1 parent 5c35efc commit ce56906

10 files changed

+243
-62
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright (c) 2014, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
using NUnit.Framework;
11+
12+
namespace React.Tests.Core
13+
{
14+
[TestFixture]
15+
public class FileCacheHashTests
16+
{
17+
private const string SAMPLE_HASH = "B10A8DB164E0754105B7A99BE72E3FE5";
18+
19+
[Test]
20+
public void TestCalculateHash()
21+
{
22+
var hash = new FileCacheHash();
23+
Assert.AreEqual(SAMPLE_HASH, hash.CalculateHash("Hello World"));
24+
}
25+
26+
[Test]
27+
public void ValidateHashShouldReturnFalseForEmptyString()
28+
{
29+
var hash = new FileCacheHash();
30+
Assert.IsFalse(hash.ValidateHash(string.Empty, SAMPLE_HASH));
31+
}
32+
33+
[Test]
34+
public void ValidateHashShouldReturnFalseForNull()
35+
{
36+
var hash = new FileCacheHash();
37+
Assert.IsFalse(hash.ValidateHash(null, SAMPLE_HASH));
38+
}
39+
40+
[Test]
41+
public void ValidateHashShouldReturnFalseWhenNoHashPrefix()
42+
{
43+
var hash = new FileCacheHash();
44+
Assert.IsFalse(hash.ValidateHash("Hello World", SAMPLE_HASH));
45+
}
46+
47+
[Test]
48+
public void ValidateHashShouldReturnFalseWhenHashDoesNotMatch()
49+
{
50+
var hash = new FileCacheHash();
51+
Assert.IsFalse(hash.ValidateHash("// @hash NOTCORRECT\nHello World", SAMPLE_HASH));
52+
}
53+
54+
[Test]
55+
public void ValidateHashShouldReturnTrueWhenHashMatches()
56+
{
57+
var hash = new FileCacheHash();
58+
Assert.IsTrue(hash.ValidateHash("// @hash " + SAMPLE_HASH + "\nHello World", SAMPLE_HASH));
59+
}
60+
}
61+
}

src/React.Tests/Core/JsxTransformerTests.cs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class JsxTransformerTests
2121
private Mock<IReactEnvironment> _environment;
2222
private Mock<ICache> _cache;
2323
private Mock<IFileSystem> _fileSystem;
24+
private Mock<IFileCacheHash> _fileCacheHash;
2425
private JsxTransformer _jsxTransformer;
2526

2627
[SetUp]
@@ -33,10 +34,13 @@ public void SetUp()
3334
_fileSystem = new Mock<IFileSystem>();
3435
_fileSystem.Setup(x => x.MapPath(It.IsAny<string>())).Returns<string>(x => x);
3536

37+
_fileCacheHash = new Mock<IFileCacheHash>();
38+
3639
_jsxTransformer = new JsxTransformer(
3740
_environment.Object,
3841
_cache.Object,
39-
_fileSystem.Object
42+
_fileSystem.Object,
43+
_fileCacheHash.Object
4044
);
4145
}
4246

@@ -98,17 +102,32 @@ public void ShouldUseCacheProvider()
98102
}
99103

100104
[Test]
101-
[Ignore("Needs to be fixed")]
102-
public void ShouldUseFileSystemCache()
105+
public void ShouldUseFileSystemCacheIfHashValid()
103106
{
104107
SetUpEmptyCache();
105108
_fileSystem.Setup(x => x.FileExists("foo.generated.js")).Returns(true);
106109
_fileSystem.Setup(x => x.ReadAsString("foo.generated.js")).Returns("/* filesystem cached */");
110+
_fileCacheHash.Setup(x => x.ValidateHash(It.IsAny<string>(), It.IsAny<string>())).Returns(true);
107111

108112
var result = _jsxTransformer.TransformJsxFile("foo.jsx");
109113
Assert.AreEqual("/* filesystem cached */", result);
110114
}
111115

116+
[Test]
117+
public void ShouldTransformJsxIfFileCacheHashInvalid()
118+
{
119+
SetUpEmptyCache();
120+
_fileSystem.Setup(x => x.FileExists("foo.generated.js")).Returns(true);
121+
_fileSystem.Setup(x => x.ReadAsString("foo.generated.js")).Returns("/* filesystem cached invalid */");
122+
_fileSystem.Setup(x => x.ReadAsString("foo.jsx")).Returns("/** @jsx React.DOM */ <div>Hello World</div>");
123+
_fileCacheHash.Setup(x => x.ValidateHash(It.IsAny<string>(), It.IsAny<string>())).Returns(false);
124+
125+
_jsxTransformer.TransformJsxFile("foo.jsx");
126+
_environment.Verify(x => x.ExecuteWithLargerStackIfRequired<string>(
127+
@"global.JSXTransformer.transform(""/** @jsx React.DOM */ <div>Hello World</div>"").code"
128+
));
129+
}
130+
112131
[Test]
113132
public void ShouldTransformJsxIfNoCache()
114133
{
@@ -123,17 +142,21 @@ public void ShouldTransformJsxIfNoCache()
123142
}
124143

125144
[Test]
126-
[Ignore("Needs to be fixed")]
127145
public void ShouldSaveTransformationResult()
128146
{
129147
_fileSystem.Setup(x => x.ReadAsString("foo.jsx")).Returns("/** @jsx React.DOM */ <div>Hello World</div>");
130148
_environment.Setup(x => x.ExecuteWithLargerStackIfRequired<string>(
131149
@"global.JSXTransformer.transform(""/** @jsx React.DOM */ <div>Hello World</div>"").code"
132150
)).Returns("React.DOM.div('Hello World')");
133151

134-
var result = _jsxTransformer.TransformAndSaveJsxFile("foo.jsx");
135-
Assert.AreEqual("foo.generated.js", result);
136-
_fileSystem.Verify(x => x.WriteAsString("foo.generated.js", "React.DOM.div('Hello World')"));
152+
string result = null;
153+
_fileSystem.Setup(x => x.WriteAsString("foo.generated.js", It.IsAny<string>())).Callback(
154+
(string filename, string contents) => result = contents
155+
);
156+
157+
var resultFilename = _jsxTransformer.TransformAndSaveJsxFile("foo.jsx");
158+
Assert.AreEqual("foo.generated.js", resultFilename);
159+
StringAssert.EndsWith("React.DOM.div('Hello World')", result);
137160
}
138161

139162
private void SetUpEmptyCache()

src/React.Tests/Core/ReactEnvironmentTest.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,15 @@ private class Mocks
7373
public Mock<IReactSiteConfiguration> Config { get; private set; }
7474
public Mock<ICache> Cache { get; private set; }
7575
public Mock<IFileSystem> FileSystem { get; private set; }
76+
public Mock<IFileCacheHash> FileCacheHash { get; private set; }
7677
public Mocks()
7778
{
7879
Engine = new Mock<IJsEngine>();
7980
EngineFactory = new Mock<IJavaScriptEngineFactory>();
8081
Config = new Mock<IReactSiteConfiguration>();
8182
Cache = new Mock<ICache>();
8283
FileSystem = new Mock<IFileSystem>();
84+
FileCacheHash = new Mock<IFileCacheHash>();
8385

8486
EngineFactory.Setup(x => x.GetEngineForCurrentThread(It.IsAny<Action<IJsEngine>>())).Returns(Engine.Object);
8587
}
@@ -90,7 +92,8 @@ public ReactEnvironment CreateReactEnvironment()
9092
EngineFactory.Object,
9193
Config.Object,
9294
Cache.Object,
93-
FileSystem.Object
95+
FileSystem.Object,
96+
FileCacheHash.Object
9497
);
9598
}
9699
}

src/React.Tests/React.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
<Compile Include="..\SharedAssemblyVersionInfo.cs">
6767
<Link>Properties\SharedAssemblyVersionInfo.cs</Link>
6868
</Compile>
69+
<Compile Include="Core\FileCacheHashTests.cs" />
6970
<Compile Include="Core\JavaScriptEngineFactoryTest.cs" />
7071
<Compile Include="Core\JsxTransformerTests.cs" />
7172
<Compile Include="Core\ReactComponentTest.cs" />

src/React/AssemblyRegistration.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,7 @@ public void Register(TinyIoCContainer container)
3535
{
3636
// One instance shared for the whole app
3737
container.Register<IReactSiteConfiguration>((c, o) => ReactSiteConfiguration.Configuration);
38-
// Force MSIE to use Chakra ActiveScript engine.
39-
// Chakra JS RT engine runs out of stack space when processing JSX
40-
container.Register<MsieConfiguration>(new MsieConfiguration
41-
{
42-
EngineMode = JsEngineMode.ChakraActiveScript
43-
});
38+
container.Register<IFileCacheHash, FileCacheHash>().AsSingleton();
4439

4540
container.Register<IReactEnvironment, ReactEnvironment>().AsPerRequestSingleton();
4641
container.Register<IJavaScriptEngineFactory, JavaScriptEngineFactory>().AsPerRequestSingleton();

src/React/FileCacheHash.cs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright (c) 2014, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
using System;
11+
using System.Security.Cryptography;
12+
using System.Text;
13+
14+
namespace React
15+
{
16+
/// <summary>
17+
/// Handles calculating a hash value for validating a file-based cache.
18+
/// </summary>
19+
public class FileCacheHash : IFileCacheHash
20+
{
21+
/// <summary>
22+
/// Prefix used for hash line in transformed file. Used for caching.
23+
/// </summary>
24+
internal const string HASH_PREFIX = "// @hash ";
25+
26+
/// <summary>
27+
/// Althorithm for calculating file hashes
28+
/// </summary>
29+
private readonly HashAlgorithm _hash = MD5.Create();
30+
31+
/// <summary>
32+
/// Calculates a hash for the specified input
33+
/// </summary>
34+
/// <param name="input">Input string</param>
35+
/// <returns>Hash of the input</returns>
36+
public string CalculateHash(string input)
37+
{
38+
var inputBytes = Encoding.UTF8.GetBytes(input);
39+
var hash = _hash.ComputeHash(inputBytes);
40+
return BitConverter.ToString(hash).Replace("-", string.Empty);
41+
}
42+
43+
/// <summary>
44+
/// Validates that the cache's hash is valid. This is used to ensure the input has not
45+
/// changed, and to invalidate the cache if so.
46+
/// </summary>
47+
/// <param name="cacheContents">Contents retrieved from cache</param>
48+
/// <param name="hash">Hash of the input</param>
49+
/// <returns><c>true</c> if the cache is still valid</returns>
50+
public bool ValidateHash(string cacheContents, string hash)
51+
{
52+
if (string.IsNullOrWhiteSpace(cacheContents))
53+
{
54+
return false;
55+
}
56+
57+
// Check if first line is hash
58+
var firstLineBreak = cacheContents.IndexOfAny(new[] { '\r', '\n' });
59+
if (firstLineBreak == -1)
60+
{
61+
return false;
62+
}
63+
var firstLine = cacheContents.Substring(0, firstLineBreak);
64+
if (!firstLine.StartsWith(HASH_PREFIX))
65+
{
66+
// Cache doesn't have hash - Err on the side of caution and invalidate it.
67+
return false;
68+
}
69+
var cacheHash = firstLine.Replace(HASH_PREFIX, string.Empty);
70+
return cacheHash == hash;
71+
}
72+
73+
/// <summary>
74+
/// Prepends the hash prefix to the hash
75+
/// </summary>
76+
/// <param name="hash">Hash to prepend prefix to</param>
77+
/// <returns>Hash with prefix</returns>
78+
public string AddPrefix(string hash)
79+
{
80+
return HASH_PREFIX + hash;
81+
}
82+
}
83+
}

src/React/IFileCacheHash.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2014, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
namespace React
11+
{
12+
/// <summary>
13+
/// Handles calculating a hash value for validating a file-based cache.
14+
/// </summary>
15+
public interface IFileCacheHash
16+
{
17+
/// <summary>
18+
/// Calculates a hash for the specified input
19+
/// </summary>
20+
/// <param name="input">Input string</param>
21+
/// <returns>Hash of the input</returns>
22+
string CalculateHash(string input);
23+
24+
/// <summary>
25+
/// Validates that the cache's hash is valid. This is used to ensure the input has not
26+
/// changed, and to invalidate the cache if so.
27+
/// </summary>
28+
/// <param name="cacheContents">Contents retrieved from cache</param>
29+
/// <param name="hash">Hash of the input</param>
30+
/// <returns><c>true</c> if the cache is still valid</returns>
31+
bool ValidateHash(string cacheContents, string hash);
32+
33+
/// <summary>
34+
/// Prepends the hash prefix to the hash
35+
/// </summary>
36+
/// <param name="hash">Hash to prepend prefix to</param>
37+
/// <returns>Hash with prefix</returns>
38+
string AddPrefix(string hash);
39+
}
40+
}

0 commit comments

Comments
 (0)