// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
///
/// Reader and, if GENERATE_BASELINES is defined, writer for files compiled into an assembly as resources.
///
/// Inspired by Razor's BaselineWriter and TestFile test classes.
public static class ResourceFile
{
private static object writeLock = new object();
///
/// Return for from 's
/// manifest.
///
/// The containing .
///
/// Name of the manifest resource in . A path relative to the test project
/// directory.
///
///
/// If true is used as a source file and must exist. Otherwise
/// is an output file and, if GENERATE_BASELINES is defined, it will
/// soon be generated if missing.
///
///
/// for from 's
/// manifest. null if GENERATE_BASELINES is defined, is
/// false, and is not found in .
///
///
/// Thrown if GENERATE_BASELINES is not defined or is true and
/// is not found in .
///
public static Stream GetResourceStream(Assembly assembly, string resourceName, bool sourceFile)
{
// The DNX runtime compiles every file under the resources folder as a resource available at runtime with
// the same name as the file name.
var fullName = $"{ assembly.GetName().Name }.{ resourceName.Replace('/', '.') }";
if (!Exists(assembly, fullName))
{
#if GENERATE_BASELINES
if (sourceFile)
{
// Even when generating baselines, a missing source file is a serious problem.
Assert.True(false, $"Manifest resource: { fullName } not found.");
}
#else
// When not generating baselines, a missing source or output file is always an error.
Assert.True(false, $"Manifest resource '{ fullName }' not found.");
#endif
return null;
}
return assembly.GetManifestResourceStream(fullName);
}
///
/// Return content of from 's
/// manifest.
///
/// The containing .
///
/// Name of the manifest resource in . A path relative to the test project
/// directory.
///
///
/// If true is used as a source file and must exist. Otherwise
/// is an output file and, if GENERATE_BASELINES is defined, it will
/// soon be generated if missing.
///
///
/// A which on completion returns the content of
/// from 's manifest. null if
/// GENERATE_BASELINES is defined, is false, and
/// is not found in .
///
///
/// Thrown if GENERATE_BASELINES is not defined or is true and
/// is not found in .
///
/// Normalizes line endings to .
public static async Task ReadResourceAsync(Assembly assembly, string resourceName, bool sourceFile)
{
using (var stream = GetResourceStream(assembly, resourceName, sourceFile))
{
if (stream == null)
{
return null;
}
using (var streamReader = new StreamReader(stream))
{
var content = await streamReader.ReadToEndAsync();
// Normalize line endings to Environment.NewLine. This removes core.autocrlf, core.eol,
// core.safecrlf, and .gitattributes from the equation and matches what MVC returns.
return content
.Replace("\r", string.Empty)
.Replace("\n", Environment.NewLine);
}
}
}
///
/// Return content of from 's
/// manifest.
///
/// The containing .
///
/// Name of the manifest resource in . A path relative to the test project
/// directory.
///
///
/// If true is used as a source file and must exist. Otherwise
/// is an output file and, if GENERATE_BASELINES is defined, it will
/// soon be generated if missing.
///
///
/// The content of from 's
/// manifest. null if GENERATE_BASELINES is defined, is
/// false, and is not found in .
///
///
/// Thrown if GENERATE_BASELINES is not defined or is true and
/// is not found in .
///
/// Normalizes line endings to .
public static string ReadResource(Assembly assembly, string resourceName, bool sourceFile)
{
using (var stream = GetResourceStream(assembly, resourceName, sourceFile))
{
if (stream == null)
{
return null;
}
using (var streamReader = new StreamReader(stream))
{
var content = streamReader.ReadToEnd();
// Normalize line endings to Environment.NewLine. This removes core.autocrlf, core.eol,
// core.safecrlf, and .gitattributes from the equation and matches what MVC returns.
return content
.Replace("\r", string.Empty)
.Replace("\n", Environment.NewLine);
}
}
}
///
/// Write to file that will become in
/// the next time the project is built. Does nothing if
/// and already match.
///
/// The containing .
///
/// Name of the manifest resource in . A path relative to the test project
/// directory.
///
///
/// Current content of . null if does
/// not currently exist in .
///
///
/// New content of in .
///
[Conditional("GENERATE_BASELINES")]
public static void UpdateFile(Assembly assembly, string resourceName, string previousContent, string content)
{
if (!string.Equals(previousContent, content, StringComparison.Ordinal))
{
// The DNX runtime compiles every file under the resources folder as a resource available at runtime with
// the same name as the file name. Need to update this file on disc.
var projectName = assembly.GetName().Name;
var projectPath = GetProjectPath(projectName);
var fullPath = Path.Combine(projectPath, resourceName);
WriteFile(fullPath, content);
}
}
private static bool Exists(Assembly assembly, string fullName)
{
var resourceNames = assembly.GetManifestResourceNames();
foreach (var resourceName in resourceNames)
{
// Resource names are case-sensitive.
if (string.Equals(fullName, resourceName, StringComparison.Ordinal))
{
return true;
}
}
return false;
}
private static string GetProjectPath(string projectName)
{
// Initial guess: Already in the project directory.
var projectPath = Path.GetFullPath(".");
var currentDirectoryName = new DirectoryInfo(projectPath).Name;
if (!string.Equals(projectName, currentDirectoryName, StringComparison.Ordinal))
{
// Not running from test project directory. Should be in "test" or solution directory.
if (string.Equals("test", currentDirectoryName, StringComparison.Ordinal))
{
projectPath = Path.Combine(projectPath, projectName);
}
else
{
projectPath = Path.Combine(projectPath, "test", projectName);
}
}
return projectPath;
}
private static void WriteFile(string fullPath, string content)
{
// Serialize writes to minimize contention for file handles and directory access.
lock (writeLock)
{
// Write content to the file, creating it if necessary.
using (var stream = File.Open(fullPath, FileMode.Create, FileAccess.Write))
{
using (var writer = new StreamWriter(stream))
{
writer.Write(content);
}
}
}
}
}
}