Skip to content

Commit 75d744b

Browse files
brdeyoRudra Pratap SIngh
authored andcommitted
Add a hash of the VC profile executing to the telemetry output. (microsoft#705)
1 parent 4a63878 commit 75d744b

14 files changed

Lines changed: 688 additions & 118 deletions

File tree

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.2.2
1+
3.2.3

src/VirtualClient/VirtualClient.Common/Extensions/CollectionExtensions.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ namespace VirtualClient.Common.Extensions
66
using System;
77
using System.Collections.Generic;
88
using System.Linq;
9+
using System.Numerics;
10+
using System.Security.Cryptography;
11+
using System.Text;
912

1013
/// <summary>
1114
/// Extensions methods for collection classes used as part of the standard
@@ -78,6 +81,38 @@ public static void AddRange<T>(this HashSet<T> dictionary, IEnumerable<T> items)
7881
}
7982
}
8083

84+
/// <summary>
85+
/// Computes a repeatable, predictable hash code for the byte array value provided.
86+
/// </summary>
87+
/// <param name="values">The string values from which to compute the hash code.</param>
88+
/// <returns>A repeatable, predictable hash code.</returns>
89+
public static BigInteger ComputeHashCode(this IEnumerable<string> values)
90+
{
91+
if (values?.Any() != true)
92+
{
93+
return -1;
94+
}
95+
96+
byte[] hashBytes = SHA1.HashData(Encoding.UTF8.GetBytes(string.Join("|", values).ToUpperInvariant()));
97+
98+
// Normalize Endianness
99+
// SHA-1 output is naturally Big-Endian.
100+
// BigInteger constructor in .NET expects Little-Endian by default.
101+
// To be safe across architectures, we force a specific order.
102+
if (!BitConverter.IsLittleEndian)
103+
{
104+
Array.Reverse(hashBytes);
105+
}
106+
107+
// Ensure the integer is interpreted as positive
108+
// BigInteger uses the last bit for the sign (positive/negative).
109+
// To ensure a positive number, we append a zero byte.
110+
byte[] unsignedBytes = new byte[hashBytes.Length + 1];
111+
Buffer.BlockCopy(hashBytes, 0, unsignedBytes, 0, hashBytes.Length);
112+
113+
return new BigInteger(unsignedBytes);
114+
}
115+
81116
/// <summary>
82117
/// Extensions returns true/false whether the two dictionary sets are semantically equal.
83118
/// </summary>

src/VirtualClient/VirtualClient.Contracts.UnitTests/ExecutionProfileElementTests.cs

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,178 @@ public void ExecutionProfileElementIsJsonSerializableWithSequentialExecutionDefi
9696
SerializationAssert.IsJsonSerializable<ExecutionProfileElement>(element);
9797
}
9898

99+
[Test]
100+
public void ExecutionProfileElementRepresentsStringConversionSemanticsCorrectly()
101+
{
102+
ExecutionProfileElement element = new ExecutionProfileElement(
103+
"AnyType1",
104+
new Dictionary<string, IConvertible>
105+
{
106+
{ "Parameter1", "ValueA" },
107+
{ "Parameter2", 123.45 },
108+
{ "Parameter3", true },
109+
{ "Parameter4", "00:01:00" }
110+
},
111+
new Dictionary<string, IConvertible>
112+
{
113+
{ "Metadata1", "ValueB" },
114+
{ "Metadata2", 6789.123 },
115+
{ "Metadata3", false },
116+
{ "Metadata4", "00:02:30" }
117+
});
118+
119+
element.ComponentType = ComponentType.Monitor;
120+
121+
string expectedValue = "Type:AnyType1,,,ComponentType:Monitor,,,Metadata:[Metadata1=ValueB;Metadata2=6789.123;Metadata3=False;Metadata4=00:02:30],,,Parameters:[Parameter1=ValueA;Parameter2=123.45;Parameter3=True;Parameter4=00:01:00]";
122+
string actualValue = element.ToString();
123+
124+
Assert.AreEqual(expectedValue, actualValue);
125+
}
126+
127+
[Test]
128+
public void ExecutionProfileElementRepresentsStringConversionSemanticsCorrectly_2()
129+
{
130+
ExecutionProfileElement element = new ExecutionProfileElement(
131+
"AnyType1",
132+
new Dictionary<string, IConvertible>
133+
{
134+
{ "Parameter1", "ValueA" }
135+
},
136+
new Dictionary<string, IConvertible>
137+
{
138+
{ "Metadata1", "ValueB" }
139+
},
140+
new List<ExecutionProfileElement>
141+
{
142+
new ExecutionProfileElement(
143+
"AnyType2",
144+
new Dictionary<string, IConvertible>
145+
{
146+
{ "Parameter1", "ValueC" }
147+
},
148+
new Dictionary<string, IConvertible>
149+
{
150+
{ "Metadata1", "ValueD" }
151+
})
152+
});
153+
154+
element.ComponentType = ComponentType.Action;
155+
element.Components.First().ComponentType = ComponentType.Action;
156+
element.Components.First().Extensions.Add("Extension1", JToken.FromObject("{ 'key1': 'value1', 'key2': [ 'one', 'two'], 'key3': { 'any': 'value', 'in': 'the', 'dictionary': 'true' } }"));
157+
158+
string expectedValue =
159+
"Type:AnyType1,,,ComponentType:Action,,,Metadata:[Metadata1=ValueB],,,Parameters:[Parameter1=ValueA],,," +
160+
"Components:[(Type:AnyType2,,,ComponentType:Action,,,Metadata:[Metadata1=ValueD],,,Parameters:[Parameter1=ValueC],,,Extensions:[(Extension1={'key1':'value1','key2':['one','two'],'key3':{'any':'value','in':'the','dictionary':'true'}})])]";
161+
162+
string actualValue = element.ToString();
163+
164+
Assert.AreEqual(expectedValue, actualValue);
165+
}
166+
167+
[Test]
168+
public void ExecutionProfileElementRepresentsStringConversionSemanticsCorrectly_3()
169+
{
170+
ExecutionProfileElement element = new ExecutionProfileElement(
171+
"AnyType1",
172+
new Dictionary<string, IConvertible>
173+
{
174+
{ "Parameter1", "ValueA" }
175+
},
176+
new Dictionary<string, IConvertible>
177+
{
178+
{ "Metadata1", "ValueB" }
179+
},
180+
new List<ExecutionProfileElement>
181+
{
182+
new ExecutionProfileElement(
183+
"AnyType2",
184+
new Dictionary<string, IConvertible>
185+
{
186+
{ "Parameter1", "ValueC" }
187+
},
188+
new Dictionary<string, IConvertible>
189+
{
190+
{ "Metadata1", "ValueD" }
191+
},
192+
new List<ExecutionProfileElement>
193+
{
194+
new ExecutionProfileElement(
195+
"AnyType3",
196+
new Dictionary<string, IConvertible>
197+
{
198+
{ "Parameter1", "ValueE" }
199+
},
200+
new Dictionary<string, IConvertible>
201+
{
202+
{ "Metadata1", "ValueF" }
203+
})
204+
})
205+
});
206+
207+
element.ComponentType = ComponentType.Action;
208+
string expectedValue =
209+
"Type:AnyType1,,,ComponentType:Action,,,Metadata:[Metadata1=ValueB],,,Parameters:[Parameter1=ValueA],,," +
210+
"Components:[(Type:AnyType2,,,ComponentType:Undefined,,,Metadata:[Metadata1=ValueD],,,Parameters:[Parameter1=ValueC],,,Components:[(Type:AnyType3,,,ComponentType:Undefined,,,Metadata:[Metadata1=ValueF],,,Parameters:[Parameter1=ValueE])])]";
211+
212+
string actualValue = element.ToString();
213+
214+
Assert.AreEqual(expectedValue, actualValue);
215+
}
216+
217+
[Test]
218+
public void ExecutionProfileElementRepresentsStringConversionSemanticsCorrectly_4()
219+
{
220+
ExecutionProfileElement element = new ExecutionProfileElement(
221+
"AnyType1",
222+
new Dictionary<string, IConvertible>
223+
{
224+
{ "Parameter1", "ValueA" }
225+
},
226+
new Dictionary<string, IConvertible>
227+
{
228+
{ "Metadata1", "ValueB" }
229+
},
230+
new List<ExecutionProfileElement>
231+
{
232+
new ExecutionProfileElement(
233+
"AnyType2",
234+
new Dictionary<string, IConvertible>
235+
{
236+
{ "Parameter1", "ValueC" }
237+
},
238+
new Dictionary<string, IConvertible>
239+
{
240+
{ "Metadata1", "ValueD" }
241+
},
242+
new List<ExecutionProfileElement>
243+
{
244+
new ExecutionProfileElement(
245+
"AnyType3",
246+
new Dictionary<string, IConvertible>
247+
{
248+
{ "Parameter1", "ValueE" }
249+
},
250+
new Dictionary<string, IConvertible>
251+
{
252+
{ "Metadata1", "ValueF" }
253+
})
254+
})
255+
});
256+
257+
element.ComponentType = ComponentType.Dependency;
258+
element.Extensions.Add("Extension1", JToken.FromObject("{ 'key1': 'value1', 'key2': [ 'one', 'two'], 'key3': { 'any': 'value', 'in': 'the', 'dictionary': 'true' } }"));
259+
260+
string expectedValue =
261+
"Type:AnyType1,,,ComponentType:Dependency,,,Metadata:[Metadata1=ValueB],,,Parameters:[Parameter1=ValueA],,," +
262+
"Components:[(Type:AnyType2,,,ComponentType:Undefined,,,Metadata:[Metadata1=ValueD],,,Parameters:[Parameter1=ValueC],,," +
263+
"Components:[(Type:AnyType3,,,ComponentType:Undefined,,,Metadata:[Metadata1=ValueF],,,Parameters:[Parameter1=ValueE])])],,," +
264+
"Extensions:[(Extension1={'key1':'value1','key2':['one','two'],'key3':{'any':'value','in':'the','dictionary':'true'}})]";
265+
266+
string actualValue = element.ToString();
267+
268+
Assert.AreEqual(expectedValue, actualValue);
269+
}
270+
99271
[Test]
100272
public void ExecutionProfileElementImplementsHashCodeSemanticsCorrectly()
101273
{
@@ -104,6 +276,135 @@ public void ExecutionProfileElementImplementsHashCodeSemanticsCorrectly()
104276
EqualityAssert.CorrectlyImplementsHashcodeSemantics<ExecutionProfileElement>(() => element, () => element2);
105277
}
106278

279+
[Test]
280+
public void ExecutionProfileElementImplementsHashCodeSemanticsCorrectly_2()
281+
{
282+
ExecutionProfileElement element1 = new ExecutionProfileElement(
283+
"AnyType1",
284+
new Dictionary<string, IConvertible>
285+
{
286+
{ "Parameter1", "ValueA" },
287+
{ "Parameter2", 123.45 },
288+
{ "Parameter3", true },
289+
{ "Parameter4", "00:01:00" }
290+
},
291+
new Dictionary<string, IConvertible>
292+
{
293+
{ "Metadata1", "ValueB" },
294+
{ "Metadata2", 6789.123 },
295+
{ "Metadata3", false },
296+
{ "Metadata4", "00:02:30" }
297+
});
298+
299+
element1.ComponentType = ComponentType.Action;
300+
301+
ExecutionProfileElement element2 = new ExecutionProfileElement(
302+
"AnyType2",
303+
new Dictionary<string, IConvertible>
304+
{
305+
{ "Parameter5", "ValueC" },
306+
{ "Parameter6", 987.65 },
307+
{ "Parameter7", true },
308+
{ "Parameter8", "00:03:00" }
309+
},
310+
new Dictionary<string, IConvertible>
311+
{
312+
{ "Metadata1", "ValueD" },
313+
{ "Metadata2", 777.88 },
314+
{ "Metadata3", false },
315+
{ "Metadata4", "00:04:15" }
316+
});
317+
318+
element2.ComponentType = ComponentType.Action;
319+
320+
EqualityAssert.CorrectlyImplementsHashcodeSemantics<ExecutionProfileElement>(() => element1, () => element2);
321+
}
322+
323+
[Test]
324+
public void ExecutionProfileElementImplementsHashCodeSemanticsCorrectly_3()
325+
{
326+
ExecutionProfileElement element1 = new ExecutionProfileElement(
327+
"AnyType1",
328+
new Dictionary<string, IConvertible>
329+
{
330+
{ "Parameter1", "ValueA" }
331+
},
332+
new Dictionary<string, IConvertible>
333+
{
334+
{ "Metadata1", "ValueB" }
335+
},
336+
new List<ExecutionProfileElement>
337+
{
338+
new ExecutionProfileElement(
339+
"AnyType2",
340+
new Dictionary<string, IConvertible>
341+
{
342+
{ "Parameter1", "ValueC" }
343+
},
344+
new Dictionary<string, IConvertible>
345+
{
346+
{ "Metadata1", "ValueD" }
347+
},
348+
new List<ExecutionProfileElement>
349+
{
350+
new ExecutionProfileElement(
351+
"AnyType3",
352+
new Dictionary<string, IConvertible>
353+
{
354+
{ "Parameter1", "ValueE" }
355+
},
356+
new Dictionary<string, IConvertible>
357+
{
358+
{ "Metadata1", "ValueF" }
359+
})
360+
})
361+
});
362+
363+
element1.ComponentType = ComponentType.Action;
364+
string here = element1.ToString();
365+
366+
ExecutionProfileElement element2 = new ExecutionProfileElement(
367+
"AnyType4",
368+
new Dictionary<string, IConvertible>
369+
{
370+
{ "Parameter1", "ValueG" }
371+
},
372+
new Dictionary<string, IConvertible>
373+
{
374+
{ "Metadata1", "ValueH" }
375+
},
376+
new List<ExecutionProfileElement>
377+
{
378+
new ExecutionProfileElement(
379+
"AnyType5",
380+
new Dictionary<string, IConvertible>
381+
{
382+
{ "Parameter1", "ValueI" }
383+
},
384+
new Dictionary<string, IConvertible>
385+
{
386+
{ "Metadata1", "ValueJ" }
387+
},
388+
new List<ExecutionProfileElement>
389+
{
390+
new ExecutionProfileElement(
391+
"AnyType6",
392+
new Dictionary<string, IConvertible>
393+
{
394+
{ "Parameter1", "ValueK" }
395+
},
396+
new Dictionary<string, IConvertible>
397+
{
398+
{ "Metadata1", "ValueL" }
399+
})
400+
})
401+
});
402+
403+
element2.ComponentType = ComponentType.Action;
404+
405+
EqualityAssert.CorrectlyImplementsHashcodeSemantics<ExecutionProfileElement>(() => element1, () => element2);
406+
}
407+
107408
[Test]
108409
public void ExecutionProfileElementImplementsEqualitySemanticsCorrectly()
109410
{

0 commit comments

Comments
 (0)