Skip to content

Commit

Permalink
tests
Browse files Browse the repository at this point in the history
  • Loading branch information
brettsam committed Dec 13, 2024
1 parent 4c1709b commit a80e6d8
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@
<ProjectReference Include="..\..\src\Azure.Functions.Cli\Azure.Functions.Cli.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="Dockerfile">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="run-test.sh">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<PropertyGroup>
<RunSettingsFilePath>$(MSBuildProjectDirectory)\E2E\StartTests_default.runsettings</RunSettingsFilePath>
</PropertyGroup>
Expand Down
14 changes: 14 additions & 0 deletions test/Azure.Functions.Cli.Tests/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM mcr.microsoft.com/dotnet/runtime:8.0

RUN apt-get update
RUN apt-get -y install fuse-zip

WORKDIR /tst

COPY ZippedOnWindows.zip .
COPY run-test.sh .

RUN chmod +x run-test.sh
RUN mkdir mnt

ENTRYPOINT ["./run-test.sh"]
49 changes: 49 additions & 0 deletions test/Azure.Functions.Cli.Tests/ProcessWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Diagnostics;

namespace Azure.Functions.Cli.Tests;

internal static class ProcessWrapper
{
public static void RunProcess(string fileName, string arguments, string workingDirectory, Action<string> writeOutput = null, Action<string> writeError = null)
{
TimeSpan procTimeout = TimeSpan.FromMinutes(3);

ProcessStartInfo startInfo = new()
{
CreateNoWindow = true,
UseShellExecute = false,
WorkingDirectory = workingDirectory,
FileName = fileName,
RedirectStandardError = true,
RedirectStandardOutput = true,
};

if (!string.IsNullOrEmpty(arguments))
{
startInfo.Arguments = arguments;
}

Process testProcess = Process.Start(startInfo);

bool completed = testProcess.WaitForExit((int)procTimeout.TotalMilliseconds);

if (!completed)
{
testProcess.Kill();
throw new TimeoutException($"Process '{fileName} {arguments}' in working directory '{workingDirectory}' did not complete in {procTimeout}.");
}

var standardOut = testProcess.StandardOutput.ReadToEnd();
var standardError = testProcess.StandardError.ReadToEnd();

if (testProcess.ExitCode != 0)
{
throw new InvalidOperationException($"Process '{fileName} {arguments}' in working directory '{workingDirectory}' exited with code '{testProcess.ExitCode}'.{Environment.NewLine}" +
$"Output:{Environment.NewLine}{standardOut}{Environment.NewLine}Error:{Environment.NewLine}{standardError}");
}

writeOutput?.Invoke(standardOut);
writeError?.Invoke(standardError);
}
}
155 changes: 110 additions & 45 deletions test/Azure.Functions.Cli.Tests/ZipHelperTests.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,60 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using Azure.Functions.Cli.Common;
using Azure.Functions.Cli.Helpers;
using Xunit;
using Xunit.Abstractions;

namespace Azure.Functions.Cli.Tests
{
public class ZipHelperTests
{
private ITestOutputHelper _output;

public ZipHelperTests(ITestOutputHelper output)
{
_output = output;
}

[Fact]
public async Task CreateZip_Succeeds()
{
var windowsZip = await BuildAndCopyFileToZipAsync("win-x64");
var linuxZip = await BuildAndCopyFileToZipAsync("linux-x64");

// copy the linux zip so we can include it in the docker image for validation
File.Copy(linuxZip, Path.Combine(Directory.GetCurrentDirectory(), "ZippedOnWindows.zip"), true);

if (OperatingSystem.IsWindows())
{
VerifyWindowsZip(windowsZip);
VerifyLinuxZipOnDocker(linuxZip);
}
else if (OperatingSystem.IsLinux())
{
// VerifyLinuxZip(windowsZip, "ZippedExe.exe");
}
else
{
throw new Exception("Unsupported OS");
}
}
private async Task<string> BuildAndCopyFileToZipAsync(string rid)
{
// files we'll need to zip up
const string proj = "ZippedExe";
string exe = rid.StartsWith("linux") ? proj : $"{proj}.exe";
string dll = $"{proj}.dll";
string config = $"{proj}.runtimeconfig.json";

var tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tempDir);

// Create temp files
// Create some temp files
var files = new List<string>();
for (int i = 0; i < 10; i++)
{
Expand All @@ -28,72 +63,102 @@ public async Task CreateZip_Succeeds()
files.Add(file);
}

void FindAndCopyFileToZip(string fileName)
// walk up to the 'test' directory
var dir = new DirectoryInfo(Directory.GetCurrentDirectory());
dir = dir.Parent.Parent.Parent.Parent;

// build the project for the rid
var csproj = dir.GetFiles($"{proj}.csproj", SearchOption.AllDirectories).FirstOrDefault();
var csprojDir = csproj.Directory.FullName;
ProcessWrapper.RunProcess("dotnet", $"build -r {rid}", csprojDir, writeOutput: WriteOutput);

var outPath = Path.Combine(csprojDir, "bin", "Debug", "net8.0", rid);

// copy the files to the zip dir
foreach (string fileName in new[] { exe, dll, config })
{
var dir = new DirectoryInfo(Directory.GetCurrentDirectory());
dir = dir.Parent.Parent.Parent.Parent;
var exe = dir.GetFiles(fileName, SearchOption.AllDirectories).FirstOrDefault();
var f = new DirectoryInfo(outPath).GetFiles(fileName, SearchOption.AllDirectories).FirstOrDefault();
Assert.True(exe != null, $"{fileName} not found.");

string destFile = Path.Combine(tempDir, fileName);
File.Copy(exe.FullName, destFile);
File.Copy(f.FullName, destFile);
files.Add(destFile);
}

// find and add ZippedExe to the string destExe = Path.Combine(tempDir, exeName);
FindAndCopyFileToZip("ZippedExe.exe");
FindAndCopyFileToZip("ZippedExe.dll");
FindAndCopyFileToZip("ZippedExe.runtimeconfig.json");

var stream = await ZipHelper.CreateZip(files, tempDir, new string[] { "ZippedExe.exe" });

// use our zip utilities to zip them
var zipFile = Path.Combine(tempDir, "test.zip");
var stream = await ZipHelper.CreateZip(files, tempDir, executables: new string[] { exe });
await FileSystemHelpers.WriteToFile(zipFile, stream);
Console.WriteLine($"---Zip file created at {zipFile}");

if (OperatingSystem.IsWindows())
{
VerifyWindowsZip(zipFile, "ZippedExe.exe");

// copy file to out dir so that devops can store it for linux tests
File.Copy(zipFile, Path.Combine(Directory.GetCurrentDirectory(), "ZippedOnWindows.zip"));
}
else if (OperatingSystem.IsLinux())
{
VerifyLinuxZip(zipFile, "ZippedExe.exe");
}
else
{
throw new Exception("Unsupported OS");
}
return zipFile;
}

private static void VerifyWindowsZip(string zipFile, string exeName)
private static void VerifyWindowsZip(string zipFile)
{
var unzipPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
ZipFile.ExtractToDirectory(zipFile, unzipPath);

var archive = ZipFile.OpenRead(zipFile);
string exeOutput = null;
string exeError = null;

var proc = Process.Start(new ProcessStartInfo
{
FileName = Path.Combine(unzipPath, exeName),
RedirectStandardOutput = true,
RedirectStandardError = true
});
ProcessWrapper.RunProcess(Path.Combine(unzipPath, "ZippedExe.exe"), string.Empty, unzipPath, o => exeOutput = o, e => exeError = e);

proc.WaitForExit();
Assert.Equal(string.Empty, exeError);
Assert.Equal("Hello, World!", exeOutput.Trim());
}

string output = proc.StandardOutput.ReadToEnd();
string error = proc.StandardError.ReadToEnd();
private void VerifyLinuxZipOnDocker(string linuxZip)
{
string dockerOutput = null;

void CaptureOutput(string output)
{
dockerOutput = output;
WriteOutput(output);
}

string imageName = $"{Guid.NewGuid()}:v1";
string containerName = Guid.NewGuid().ToString();

Assert.Equal(string.Empty, error);
Assert.Equal("Hello, World!", output.Trim());
var outDir = Directory.GetCurrentDirectory();
try
{
ProcessWrapper.RunProcess("docker", $"build -t {imageName} .", outDir, writeOutput: CaptureOutput);
ProcessWrapper.RunProcess("docker", $"run --name {containerName} --privileged {imageName}", outDir, writeOutput: CaptureOutput);

// output should have the dir listing of the mounted zip and end with "Hello World"
var outputLines = dockerOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries);
Assert.Equal(15, outputLines.Length);

// ignore first ('total ...') and last ('Hello, World!') to validate file perms
foreach (string line in outputLines[1..^1])
{
// exe should be executable
if (line.EndsWith("ZippedExe"))
{
Assert.StartsWith("-rwxrwxrwx", line);
}
else
{
Assert.StartsWith("-rw-rw-rw-", line);
}
}
Assert.Equal("Hello, World!", outputLines.Last());
}
finally
{
// clean up
ProcessWrapper.RunProcess("docker", $"rm --force {containerName}", outDir, writeOutput: CaptureOutput);
ProcessWrapper.RunProcess("docker", $"rmi --force {imageName}", outDir, writeOutput: CaptureOutput);
}
}

private static void VerifyLinuxZip(string zipFile, string exeName)
{
VerifyWindowsZip(zipFile, exeName);
}

private void WriteOutput(string output)
{
_output.WriteLine(output);
}
}
}
11 changes: 11 additions & 0 deletions test/Azure.Functions.Cli.Tests/run-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#! /bin/bash

# this is what our hosting environment does; we need to validate we can run the exe when mounted like this
fuse-zip ./ZippedOnWindows.zip ./mnt -r

# print out directory for debugging
cd ./mnt
ls -l

# run the exe to make sure it works
./ZippedExe
2 changes: 1 addition & 1 deletion test/ZippedExe/ZippedExe.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down

0 comments on commit a80e6d8

Please sign in to comment.