Skip to content

Commit 7169774

Browse files
committed
LT-14596: Automate Testing of Patch Installation
- Add a target to build an installer and a test patch so that we can quickly test that each FLEx file is patched - Add a build step to record files expected to be installed (binaries installed by our installer; *not* merge modules, etc.) - Install a utility to read this list and verify installation Change-Id: I3dcfdcaa414075d6bd645ff084f04382cf9dca0f
1 parent bc98a96 commit 7169774

12 files changed

Lines changed: 593 additions & 16 deletions

File tree

Build/FieldWorks.proj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<UsingTask TaskName="GenerateFwTargets" AssemblyFile="FwBuildTasks.dll"/>
1616
<UsingTask TaskName="GenerateNUnitReports" AssemblyFile="FwBuildTasks.dll" />
1717
<UsingTask TaskName="GenerateTestCoverageReport" AssemblyFile="FwBuildTasks.dll"/>
18+
<UsingTask TaskName="LogMetadata" AssemblyFile="FwBuildTasks.dll"/>
1819
<UsingTask TaskName="Make" AssemblyFile="FwBuildTasks.dll"/>
1920
<UsingTask TaskName="Md5Checksum" AssemblyFile="FwBuildTasks.dll"/>
2021
<UsingTask TaskName="NUnit" AssemblyFile="FwBuildTasks.dll"/>
@@ -60,8 +61,9 @@
6061
<Import Project="Windows.targets"/>
6162
<Import Project="FieldWorks.targets" Condition="Exists('FieldWorks.targets')"/>
6263
<Import Project="mkall.targets"/>
63-
<Import Project="Installer.targets"/>
64+
<Import Project="Installer.targets" Condition="'$(OS)'=='Windows_NT'"/>
65+
<Import Project="InstallerTests.targets" Condition="'$(OS)'=='Windows_NT'"/>
66+
<Import Project="LocalLibrary.targets"/>
6467
<Import Project="Localize.targets"/>
6568
<Import Project="NuGet.targets"/>
66-
<Import Project="LocalLibrary.targets"/>
6769
</Project>

Build/FwBuildTasks.dll

1 KB
Binary file not shown.

Build/Installer.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@
266266
<Copy SourceFiles="@(BinFiles)" OverwriteReadonlyFiles="true" DestinationFolder="$(AppBuildDir)/$(BinDirSuffix)/%(RecursiveDir)"/>
267267
<Copy SourceFiles="@(CustomInstallFiles)" OverwriteReadonlyFiles="true" DestinationFolder="$(AppBuildDir)/$(BinDirSuffix)/.."/>
268268
<Copy SourceFiles="@(OverrideFiles)" OverwriteReadonlyFiles="true" DestinationFolder="$(OverridesDestDir)"/>
269-
<CallTarget Targets="HarvestL10n;ConvertHarvestsToWxi"/>
269+
<CallTarget Targets="HarvestL10n;ConvertHarvestsToWxi;WriteFilesMetadata"/>
270270
</Target>
271271

272272
<Target Name="HarvestL10n">

Build/InstallerTests.targets

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="15.0">
3+
<PropertyGroup>
4+
<ReposRoot Condition="'$(ReposRoot)'==''">$(fwrt)\..</ReposRoot>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<!-- <ReposToTestPatching Include="$(ReposRoot)\libpalaso"/> -->
8+
<!-- <ReposToTestPatching Include="$(ReposRoot)\liblcm"/> -->
9+
<!-- <ReposToTestPatching Include="$(ReposRoot)\chorus"/> -->
10+
<ReposToTestPatching Include="$(fwrt)"/>
11+
<!-- <ReposToTestPatching Include="$(fwrt)\Localization"/>, helps? -->
12+
</ItemGroup>
13+
14+
<!-- ########################################################################################################## -->
15+
<!-- Builds a base installer, makes changes in each repository (by applying the stash), then builds a patch to test -->
16+
<!-- ########################################################################################################## -->
17+
<Target Name="BuildPatchTest">
18+
<MSBuild Projects="$(MSBuildProjectFullPath)" Targets="BuildBaseInstaller"/>
19+
<Exec Command="git stash apply" WorkingDirectory="%(ReposToTestPatching.Identity)"/>
20+
<MSBuild Projects="$(MSBuildProjectFullPath)" Properties="BuildVersionSegment=3" Targets="BuildPatchInstaller"/>
21+
</Target>
22+
23+
<!-- ########################################################################################################## -->
24+
<!-- Builds a base installer, changes each installed file, then builds a patch to test -->
25+
<!-- Note: you must build target BuildBaseInstaller before using this test. -->
26+
<!-- ########################################################################################################## -->
27+
<Target Name="BuildQuickPatchTest" DependsOnTargets="InstallerVersionNumbers;CleanMasterOutputDir;FieldWorks">
28+
<MSBuild Projects="$(MSBuildProjectFullPath)" Properties="BuildingBaseInstaller=true"
29+
Targets="CopyFilesToInstall;WriteFilesMetadata;BuildProductBaseMsi;CopyBuildToMaster"/>
30+
<ItemGroup>
31+
<HelloWorldLines Include='using System%3B'/>
32+
<HelloWorldLines Include='public class FieldWorks{'/>
33+
<HelloWorldLines Include='public static void Main(string[] args){'/>
34+
<HelloWorldLines Include='Console.WriteLine("Hello, World")%3B}}'/>
35+
<FlexCode Include="$(fwrt)\Src\Common\FieldWorks\**\*.cs"/>
36+
</ItemGroup>
37+
<WriteLinesToFile File="%(FlexCode.Identity)" Overwrite="true" Lines="//"/>
38+
<WriteLinesToFile File="$(fwrt)\Src\Common\FieldWorks\FieldWorks.cs" Overwrite="true" Lines="@(HelloWorldLines)"/>
39+
<MSBuild Projects="$(MSBuildProjectFullPath)" Properties="BuildVersionSegment=3"
40+
Targets="FieldWorks;CopyFilesToInstall;WriteFilesMetadata;BuildProductPatchMsp"/>
41+
</Target>
42+
43+
<!-- ########################################################################################################## -->
44+
<!-- ### Write a table of files' metadata to verify that each file has been installed or patched correctly. ### -->
45+
<!-- ########################################################################################################## -->
46+
<Target Name="WriteFilesMetadata" DependsOnTargets="InstallerVersionNumbers">
47+
<PropertyGroup>
48+
<MetadataLog>$(AppBuildDir)\$(BinDirSuffix)\installerTestMetadata.$(BuildVersion).csv</MetadataLog>
49+
</PropertyGroup>
50+
<ItemGroup>
51+
<BinFilesToHash Include="$(AppBuildDir)\$(BinDirSuffix)\**\*" Exclude="$(MetadataLog)"/>
52+
<L10nFilesToHash Include="$(AppBuildDir)\$(L10nDirSuffix)\**\*"/>
53+
<FLExExeToHash
54+
</ItemGroup>
55+
<WriteLinesToFile File="$(MetadataLog)" Overwrite="true" Lines="Metadata for $(ApplicationName) $(BuildVersion)"/>
56+
<LogMetadata LogFile="$(MetadataLog)" Files="@(FilesToHash)" PathPrefixToDrop="$(AppBuildDir)\$(BinDirSuffix)\"/>
57+
</Target>
58+
</Project>

Build/Src/FwBuildTasks/FwBuildTasks.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
<ItemGroup>
110110
<Compile Include="AssemblyInfo.cs" />
111111
<Compile Include="FwBuildTasksTests\WxsToWxiTests.cs" />
112+
<Compile Include="LogMetadata.cs" />
112113
<Compile Include="WxsToWxi.cs" />
113114
<Compile Include="Clouseau.cs" />
114115
<Compile Include="CodeReader\ILReader.cs" />
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2019 SIL International
2+
// This software is licensed under the LGPL, version 2.1 or later
3+
// (http://www.gnu.org/licenses/lgpl-2.1.html)
4+
5+
using System.Diagnostics;
6+
using System.IO;
7+
using FwBuildTasks;
8+
using Microsoft.Build.Framework;
9+
using Microsoft.Build.Utilities;
10+
11+
namespace SIL.FieldWorks.Build.Tasks
12+
{
13+
/// <summary>
14+
/// Generate a CSV file containing metadata about the specified files (MD5, date modified, etc.)
15+
/// for use in testing installers and patchers
16+
/// </summary>
17+
public class LogMetadata : Task
18+
{
19+
[Required]
20+
public string[] Files { get; set; }
21+
22+
public string PathPrefixToDrop { get; set; }
23+
24+
[Required]
25+
public string LogFile { get; set; }
26+
27+
public bool Overwrite { get; set; }
28+
29+
public override bool Execute()
30+
{
31+
using (var writer = new StreamWriter(LogFile, !Overwrite))
32+
{
33+
writer.WriteLine("File,MD5,Version,Modified");
34+
var lengthToDrop = string.IsNullOrEmpty(PathPrefixToDrop) ? 0 : PathPrefixToDrop.Length;
35+
foreach (var file in Files)
36+
{
37+
var md5 = Md5Checksum.Compute(file);
38+
// Some files have commas in their versions. Replace with another character because our CSV reader is cheap.
39+
var version = FileVersionInfo.GetVersionInfo(file).FileVersion?.Replace(',', ';');
40+
var modified = File.GetLastWriteTimeUtc(file);
41+
writer.WriteLine($"{file.Substring(lengthToDrop)}, {md5}, {version}, {modified:yyyy-MM-dd HH:mm:ss}");
42+
}
43+
return true;
44+
}
45+
}
46+
}
47+
}
Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2015 SIL International
1+
// Copyright (c) 2015-2019 SIL International
22
// This software is licensed under the LGPL, version 2.1 or later
33
// (http://www.gnu.org/licenses/lgpl-2.1.html)
44

@@ -13,28 +13,19 @@ namespace FwBuildTasks
1313
{
1414
public class Md5Checksum : Task
1515
{
16+
private static readonly HashAlgorithm Hasher = HashAlgorithm.Create("MD5");
17+
1618
[Required]
1719
public string SourceFile { get; set; }
1820

1921
public override bool Execute()
2022
{
2123
try
2224
{
23-
var hasher = HashAlgorithm.Create("MD5");
24-
byte[] checksum;
25-
using (var file = File.OpenRead(SourceFile))
26-
{
27-
checksum = hasher.ComputeHash(file);
28-
}
29-
var bldr = new StringBuilder();
30-
for ( int i=0; i < checksum.Length; i++ )
31-
{
32-
bldr.Append(String.Format("{0:x2}", checksum[i]));
33-
}
3425
var outputFile = SourceFile + ".MD5";
3526
using (var writer = new StreamWriter(outputFile))
3627
{
37-
writer.Write(bldr.ToString());
28+
writer.Write(Compute(SourceFile));
3829
}
3930
return true;
4031
}
@@ -44,5 +35,21 @@ public override bool Execute()
4435
return false;
4536
}
4637
}
38+
39+
public static string Compute(string filename)
40+
{
41+
byte[] checksumBytes;
42+
using (var file = File.OpenRead(filename))
43+
{
44+
checksumBytes = Hasher.ComputeHash(file);
45+
}
46+
var bldr = new StringBuilder();
47+
foreach (var b in checksumBytes)
48+
{
49+
bldr.AppendFormat("{0:x2}", b);
50+
}
51+
52+
return bldr.ToString();
53+
}
4754
}
4855
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) 2019 SIL International
2+
// This software is licensed under the LGPL, version 2.1 or later
3+
// (http://www.gnu.org/licenses/lgpl-2.1.html)
4+
5+
using System.Diagnostics;
6+
using System.IO;
7+
using System.Security.Cryptography;
8+
using System.Text;
9+
10+
namespace SIL.InstallValidator
11+
{
12+
public class InstallValidator
13+
{
14+
// ENHANCE (Hasso) 2019.07: look for extra files?
15+
// ENHANCE (Hasso) 2019.07: option to catalog files that have already been installed (for testing against other upgrade scenarios)
16+
public static void Main(string[] args)
17+
{
18+
if (args.Length == 0 || !File.Exists(args[0]))
19+
{
20+
// The user double-clicked (poor user won't see this), ran w/o args, or ran w/ invalid args.
21+
Debug.WriteLine("SIL Installation Validator");
22+
Debug.WriteLine("Copyright (c) 2019 SIL International");
23+
Debug.WriteLine(string.Empty);
24+
Debug.WriteLine("This program may be installed in the same directory as another SIL program,");
25+
Debug.WriteLine("but users should not use it. Its purpose is to help verify that the program");
26+
Debug.WriteLine(" was installed correctly.");
27+
Debug.WriteLine("Usage:");
28+
Debug.WriteLine(" - Drop installerTestMetadata.csv on this exe to generate a report");
29+
Debug.WriteLine(" - InstallValidator.exe installerTestMetadata.csv [alternate report location]");
30+
Debug.WriteLine(" (for unit tests)");
31+
return;
32+
}
33+
34+
var logFile = SafeGetAt(args, 1) ?? Path.Combine(Path.GetTempPath(), "FlexInstallationReport.csv");
35+
36+
using (var expected = new StreamReader(args[0]))
37+
using (var actual = new StreamWriter(logFile))
38+
{
39+
actual.WriteLine("File, Result, Expected Version, Actual Version, Expected Date, Actual Date Modified (UTC)");
40+
expected.ReadLine(); // skip headers
41+
string file;
42+
while ((file = expected.ReadLine()) != null)
43+
{
44+
var info = file.Split(',');
45+
if (info.Length < 2)
46+
{
47+
actual.WriteLine($"Bad input (or EOF), {file}");
48+
continue;
49+
}
50+
51+
var filePath = info[0].Trim();
52+
actual.Write(filePath);
53+
var fullPath = Path.Combine(Directory.GetCurrentDirectory(), filePath);
54+
if (!File.Exists(fullPath))
55+
{
56+
actual.WriteLine(", is missing");
57+
continue;
58+
}
59+
60+
var expectedMd5 = info[1].Trim();
61+
var actualMd5 = ComputeMd5Sum(fullPath);
62+
if (string.Equals(expectedMd5, actualMd5))
63+
{
64+
actual.WriteLine(", was installed correctly");
65+
continue;
66+
}
67+
68+
var expectedVersion = SafeGetAt(info, 2);
69+
var actualVersion = FileVersionInfo.GetVersionInfo(fullPath).FileVersion;
70+
var expectedDate = SafeGetAt(info, 3);
71+
var actualDate = File.GetLastWriteTimeUtc(fullPath);
72+
actual.WriteLine(
73+
$", incorrect file is present, {expectedVersion}, {actualVersion}, {expectedDate}, {actualDate:yyyy-MM-dd HH:mm:ss}");
74+
}
75+
}
76+
77+
// If we ran the program by dropping installerTestMetadata.csv, open the report using the default program
78+
if (args.Length == 1)
79+
{
80+
Process.Start(logFile);
81+
}
82+
}
83+
84+
private static string SafeGetAt(string[] arr, int index)
85+
{
86+
return arr.Length > index ? arr[index].Trim() : null;
87+
}
88+
89+
private static readonly HashAlgorithm Hasher = HashAlgorithm.Create("MD5");
90+
91+
public static string ComputeMd5Sum(string filename)
92+
{
93+
byte[] checksumBytes;
94+
using (var file = File.OpenRead(filename))
95+
{
96+
checksumBytes = Hasher.ComputeHash(file);
97+
}
98+
var bldr = new StringBuilder();
99+
foreach (var b in checksumBytes)
100+
{
101+
bldr.AppendFormat("{0:x2}", b);
102+
}
103+
104+
return bldr.ToString();
105+
}
106+
}
107+
}

0 commit comments

Comments
 (0)