Skip to content

Commit

Permalink
fix(dontet): iso-8601 date strings get turned into DateTime (#2058)
Browse files Browse the repository at this point in the history
When a ISO-8601-encoded date is passed from JavaScript to the .NET app,
the JSON deserializer would interpret that as a `DateTime` instead of
passing the string un-touched. This is a problem, since the jsii kernel
protocol has a dedicated wrapper key for DateTime values (`$jsii$date`).

This PR adds a compliance test around this particular scenario, and
disabled `DateTime` handling from the standard deserializer (the wrapped
values are still processed correctly).

> This PR also changes the way the .NET test package generates the
> tested libraries (`jsii-calc` & dependencies) so that instead of
> generating NuGet packages, it only generates C# projects. This makes
> the experience of debugging the tests **much** nicer, and reduces the
> likelihood of cached build artifacts getting in the way.

Fixes aws/aws-cdk#10513
  • Loading branch information
RomainMuller authored Sep 29, 2020
1 parent 891b011 commit 52d7382
Show file tree
Hide file tree
Showing 17 changed files with 838 additions and 11 deletions.
15 changes: 13 additions & 2 deletions packages/@jsii/dotnet-runtime-test/generate.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
#!/bin/bash
set -euo pipefail
test="./test"
genRoot="${test}/generated"

# Generate NuGet packages for jsii-calc and its dependencies.
jsii-pacmak -t dotnet --recurse -o ${test}/generated ../../jsii-calc
# Clean up before we start working
rm -rf ${genRoot}

# Generate .NET projects for jsii-calc and its dependencies.
jsii-pacmak -t dotnet --code-only --recurse -o ${genRoot} ../../jsii-calc

# Hack around project references to de-duplicate Amazon.JSII.Runtime in generated code.
for csproj in ${genRoot}/dotnet/*/*.csproj
do
dotnet remove ${csproj} package Amazon.JSII.Runtime
dotnet add ${csproj} reference ../dotnet-runtime/src/Amazon.JSII.Runtime/Amazon.JSII.Runtime.csproj
done

# Generate Directory.Build.props
/usr/bin/env node ./Directory.Build.props.t.js > ${test}/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,18 @@
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="xunit"/>
<PackageReference Include="xunit.runner.visualstudio"/>
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="XunitXml.TestLogger" />
<DotNetCliToolReference Include="dotnet-xunit"/>
<DotNetCliToolReference Include="dotnet-xunit" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\dotnet-runtime\src\Amazon.JSII.Runtime\Amazon.JSII.Runtime.csproj" />
<ProjectReference Include="..\generated\dotnet\Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId\Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId.csproj" />
<ProjectReference Include="..\generated\dotnet\Amazon.JSII.Tests.CalculatorPackageId.BasePackageId\Amazon.JSII.Tests.CalculatorPackageId.BasePackageId.csproj" />
<ProjectReference Include="..\generated\dotnet\Amazon.JSII.Tests.CalculatorPackageId.LibPackageId\Amazon.JSII.Tests.CalculatorPackageId.LibPackageId.csproj" />
<ProjectReference Include="..\generated\dotnet\Amazon.JSII.Tests.CalculatorPackageId\Amazon.JSII.Tests.CalculatorPackageId.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@


Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.JSII.Runtime.IntegrationTests", "Amazon.JSII.Runtime.IntegrationTests\Amazon.JSII.Runtime.IntegrationTests.csproj", "{CE3CAFBD-25F8-422D-925A-8F9CCEA1F548}"
Expand All @@ -9,6 +9,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.JSII.JsonModel", "..
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Amazon.JSII.Runtime.IntegrationTests.FSharp", "Amazon.JSII.Runtime.IntegrationTests.FSharp\Amazon.JSII.Runtime.IntegrationTests.FSharp.fsproj", "{224406B0-12D5-408C-91E9-7420F72C2855}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "generated", "generated", "{5B76E4C5-B66F-4263-87A4-111E944AB744}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dotnet", "dotnet", "{37A4E9E1-89D6-42FC-AF9D-4C4453D29857}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId", "generated\dotnet\Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId\Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId.csproj", "{BBF82641-F1B7-44CC-BFE6-C82E6B00BC13}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.JSII.Tests.CalculatorPackageId.BasePackageId", "generated\dotnet\Amazon.JSII.Tests.CalculatorPackageId.BasePackageId\Amazon.JSII.Tests.CalculatorPackageId.BasePackageId.csproj", "{A1287897-A767-4F1E-B83E-874797851732}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.JSII.Tests.CalculatorPackageId.LibPackageId", "generated\dotnet\Amazon.JSII.Tests.CalculatorPackageId.LibPackageId\Amazon.JSII.Tests.CalculatorPackageId.LibPackageId.csproj", "{A2830012-1C9D-4D42-80B1-61A360AFA3C9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.JSII.Tests.CalculatorPackageId", "generated\dotnet\Amazon.JSII.Tests.CalculatorPackageId\Amazon.JSII.Tests.CalculatorPackageId.csproj", "{B246A5D7-FF6F-47B1-A47B-6B2B3205A2FC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -31,5 +43,28 @@ Global
{224406B0-12D5-408C-91E9-7420F72C2855}.Debug|Any CPU.Build.0 = Debug|Any CPU
{224406B0-12D5-408C-91E9-7420F72C2855}.Release|Any CPU.ActiveCfg = Release|Any CPU
{224406B0-12D5-408C-91E9-7420F72C2855}.Release|Any CPU.Build.0 = Release|Any CPU
{BBF82641-F1B7-44CC-BFE6-C82E6B00BC13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BBF82641-F1B7-44CC-BFE6-C82E6B00BC13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BBF82641-F1B7-44CC-BFE6-C82E6B00BC13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BBF82641-F1B7-44CC-BFE6-C82E6B00BC13}.Release|Any CPU.Build.0 = Release|Any CPU
{A1287897-A767-4F1E-B83E-874797851732}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1287897-A767-4F1E-B83E-874797851732}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1287897-A767-4F1E-B83E-874797851732}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1287897-A767-4F1E-B83E-874797851732}.Release|Any CPU.Build.0 = Release|Any CPU
{A2830012-1C9D-4D42-80B1-61A360AFA3C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A2830012-1C9D-4D42-80B1-61A360AFA3C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A2830012-1C9D-4D42-80B1-61A360AFA3C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A2830012-1C9D-4D42-80B1-61A360AFA3C9}.Release|Any CPU.Build.0 = Release|Any CPU
{B246A5D7-FF6F-47B1-A47B-6B2B3205A2FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B246A5D7-FF6F-47B1-A47B-6B2B3205A2FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B246A5D7-FF6F-47B1-A47B-6B2B3205A2FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B246A5D7-FF6F-47B1-A47B-6B2B3205A2FC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{37A4E9E1-89D6-42FC-AF9D-4C4453D29857} = {5B76E4C5-B66F-4263-87A4-111E944AB744}
{BBF82641-F1B7-44CC-BFE6-C82E6B00BC13} = {37A4E9E1-89D6-42FC-AF9D-4C4453D29857}
{A1287897-A767-4F1E-B83E-874797851732} = {37A4E9E1-89D6-42FC-AF9D-4C4453D29857}
{A2830012-1C9D-4D42-80B1-61A360AFA3C9} = {37A4E9E1-89D6-42FC-AF9D-4C4453D29857}
{B246A5D7-FF6F-47B1-A47B-6B2B3205A2FC} = {37A4E9E1-89D6-42FC-AF9D-4C4453D29857}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@

<ItemGroup>
<ProjectReference Include="..\..\..\dotnet-runtime\src\Amazon.JSII.Runtime\Amazon.JSII.Runtime.csproj" />
<ProjectReference Include="..\generated\dotnet\Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId\Amazon.JSII.Tests.CalculatorPackageId.BaseOfBasePackageId.csproj" />
<ProjectReference Include="..\generated\dotnet\Amazon.JSII.Tests.CalculatorPackageId.BasePackageId\Amazon.JSII.Tests.CalculatorPackageId.BasePackageId.csproj" />
<ProjectReference Include="..\generated\dotnet\Amazon.JSII.Tests.CalculatorPackageId.LibPackageId\Amazon.JSII.Tests.CalculatorPackageId.LibPackageId.csproj" />
<ProjectReference Include="..\generated\dotnet\Amazon.JSII.Tests.CalculatorPackageId\Amazon.JSII.Tests.CalculatorPackageId.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -1476,5 +1476,41 @@ public override object GiveItBack(object value) {
return value;
}
}

[Fact(DisplayName = Prefix + nameof(Iso8601DoesNotDeserializeToDate))]
public void Iso8601DoesNotDeserializeToDate()
{
var now = $"{DateTime.UtcNow.ToString("s")}Z";
var wallClock = new WallClock(now);
var entropy = new MildEntropy(wallClock);

Assert.Equal(now, entropy.Increase());
}

private sealed class WallClock: DeputyBase, IWallClock
{
private String _frozenTime;

public WallClock(String frozenTime)
{
_frozenTime = frozenTime;
}

public String Iso8601Now()
{
return _frozenTime;
}
}

private sealed class MildEntropy: Entropy
{
public MildEntropy(IWallClock clock): base(clock)
{
}
public override String Repeat(String word)
{
return word;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ TResponse ReceiveResponse<TResponse>()

TResponse TryDeserialize<TResponse>(string responseJson) where TResponse : class, IKernelResponse
{
JObject responseObject = (JObject)JsonConvert.DeserializeObject(responseJson)!;
JObject responseObject = (JObject)JsonConvert.DeserializeObject(responseJson, new JsonSerializerSettings
{
DateParseHandling = DateParseHandling.None
})!;

if (responseObject.ContainsKey("error"))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,16 @@
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
Expand Down Expand Up @@ -1776,4 +1780,26 @@ public void classesCanSelfReferenceDuringClassInitialization() {
final OuterClass outerClass = new OuterClass();
assertNotNull(outerClass.getInnerClass());
}

@Test
public void iso8601DoesNotDeserializeToDate() {
final TimeZone tz = TimeZone.getTimeZone("UTC");
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
df.setTimeZone(tz);
final String nowAsISO = df.format(new Date());

final IWallClock wallClock = new IWallClock() {
public String iso8601Now() {
return nowAsISO;
}
};

final Entropy entropy = new Entropy(wallClock) {
public String repeat(final String word) {
return word;
}
};

assertEquals(nowAsISO, entropy.increase());
}
}
22 changes: 22 additions & 0 deletions packages/@jsii/python-runtime/tests/test_compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
DisappointingCollectionSource,
DoNotOverridePrivates,
DoubleTrouble,
Entropy,
GreetingAugmenter,
IBellRinger,
IConcreteBellRinger,
Expand All @@ -37,6 +38,7 @@
InterfaceCollections,
IInterfaceWithProperties,
IStructReturningDelegate,
IWallClock,
Isomorphism,
JsiiAgent,
JSObjectLiteralForInterface,
Expand Down Expand Up @@ -1267,3 +1269,23 @@ def __init__(self):

def test_kwargs_from_superinterface_are_working():
assert Kwargs.method(extra="ordinary", prop=SomeEnum.SOME)


def test_iso8601_does_not_deserialize_to_date():
@jsii.implements(IWallClock)
class WallClock:
def __init__(self, now: str):
self.now = now

def iso8601_now(self) -> str:
return self.now

class MildEntropy(Entropy):
def repeat(self, word: str) -> str:
return word

now = datetime.utcnow().isoformat() + "Z"
wall_clock = WallClock(now)
entropy = MildEntropy(wall_clock)

assert now == entropy.increase()
58 changes: 58 additions & 0 deletions packages/jsii-calc/lib/date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* This class is used to validate that serialization and deserialization does
* not interpret ISO-8601-formatted timestampts to the native date/time object,
* as the jsii protocol has a $jsii$date wrapper for this purpose (node's JSON
* parsing does *NOT* detect dates automatically in this way, so host libraries
* should not either).
*/
export abstract class Entropy {
/**
* Creates a new instance of Entropy.
*
* @param clock your implementation of `WallClock`
*/
public constructor(private readonly clock: IWallClock) {}

/**
* Increases entropy by consuming time from the clock (yes, this is a long
* shot, please don't judge).
*
* @returns the time from the `WallClock`.
*/
public increase(): string {
const now = this.clock.iso8601Now();

if (typeof now !== 'string') {
throw new Error(
`Now should have been a string, is a ${typeof now}. Check your (de)serializer`,
);
}

const result = this.repeat(now);

if (typeof result !== 'string') {
throw new Error(
`Repeat should return a string, but returned a ${typeof result}. Check your (de)serializer`,
);
}

return result;
}

/**
* Implement this method such that it returns `word`.
* @param word the value to return.
* @returns `word`.
*/
public abstract repeat(word: string): string;
}

/**
* Implement this interface.
*/
export interface IWallClock {
/**
* Returns the current time, formatted as an ISO-8601 string.
*/
iso8601Now(): string;
}
1 change: 1 addition & 0 deletions packages/jsii-calc/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './calculator';
export * from './compliance';
export * from './date';
export * from './documented';
export * from './erasures';
export * from './nested-class';
Expand Down
Loading

0 comments on commit 52d7382

Please sign in to comment.