How to get Directory while running unit test - c#

Hi when running my unit test I'm wanting to get the directory my project is running in to retrieve a file.
Say I have a Test project named MyProject. Test I run:
AppDomain.CurrentDomain.SetupInformation.ApplicationBase
and I receive "C:\\Source\\MyProject.Test\\bin\\Debug".
This is close to what I'm after. I don't want the bin\\Debug part.
Anyone know how instead I could get "C:\\Source\\MyProject.Test\\"?

I would do it differently.
I suggest making that file part of the solution/project. Then right-click -> Properties -> Copy To Output = Copy Always.
That file will then be copied to whatever your output directory is (e.g. C:\Source\MyProject.Test\bin\Debug).
Edit: Copy To Output = Copy if Newer is the better option

Usually you retrieve your solution directory (or project directory, depending on your solution structure) like this:
string solution_dir = Path.GetDirectoryName( Path.GetDirectoryName(
TestContext.CurrentContext.TestDirectory ) );
This will give you the parent directory of the "TestResults" folder created by testing projects.

Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName;
This will give you the directory you need....
as
AppDomain.CurrentDomain.SetupInformation.ApplicationBase
gives nothing but
Directory.GetCurrentDirectory().
Have alook at this link
http://msdn.microsoft.com/en-us/library/system.appdomain.currentdomain.aspx

Further to #abhilash's comment.
This works in my EXE's, DLL's and when tested from a different UnitTest project in both Debug or Release modes:
var dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location.Replace("bin\\Debug", string.Empty));

/// <summary>
/// Testing various directory sources in a Unit Test project
/// </summary>
/// <remarks>
/// I want to mimic the web app's App_Data folder in a Unit Test project:
/// A) Using Copy to Output Directory on each data file
/// D) Without having to set Copy to Output Directory on each data file
/// </remarks>
[TestMethod]
public void UT_PathsExist()
{
// Gets bin\Release or bin\Debug depending on mode
string baseA = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
Console.WriteLine(string.Format("Dir A:{0}", baseA));
Assert.IsTrue(System.IO.Directory.Exists(baseA));
// Gets bin\Release or bin\Debug depending on mode
string baseB = AppDomain.CurrentDomain.BaseDirectory;
Console.WriteLine(string.Format("Dir B:{0}", baseB));
Assert.IsTrue(System.IO.Directory.Exists(baseB));
// Returns empty string (or exception if you use .ToString()
string baseC = (string)AppDomain.CurrentDomain.GetData("DataDirectory");
Console.WriteLine(string.Format("Dir C:{0}", baseC));
Assert.IsFalse(System.IO.Directory.Exists(baseC));
// Move up two levels
string baseD = System.IO.Directory.GetParent(baseA).Parent.FullName;
Console.WriteLine(string.Format("Dir D:{0}", baseD));
Assert.IsTrue(System.IO.Directory.Exists(baseD));
// You need to set the Copy to Output Directory on each data file
var appPathA = System.IO.Path.Combine(baseA, "App_Data");
Console.WriteLine(string.Format("Dir A/App_Data:{0}", appPathA));
// C:/solution/UnitTestProject/bin/Debug/App_Data
Assert.IsTrue(System.IO.Directory.Exists(appPathA));
// You can work with data files in the project directory's App_Data folder (or any other test data folder)
var appPathD = System.IO.Path.Combine(baseD, "App_Data");
Console.WriteLine(string.Format("Dir D/App_Data:{0}", appPathD));
// C:/solution/UnitTestProject/App_Data
Assert.IsTrue(System.IO.Directory.Exists(appPathD));
}

I normally do it like that, and then I just add "..\..\" to the path to get up to the directory I want.
So what you could do is this:
var path = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + #"..\..\";

For NUnit this is what I do:
// Get the executing directory of the tests
string dir = NUnit.Framework.TestContext.CurrentContext.TestDirectory;
// Infer the project directory from there...2 levels up (depending on project type - for asp.net omit the latter Parent for a single level up)
dir = System.IO.Directory.GetParent(dir).Parent.FullName;
If required you can from there navigate back down to other directories if required:
dir = Path.Combine(dir, "MySubDir");

According to https://github.com/nunit/nunit/issues/742#issuecomment-121964506
For NUnit3 , System.Environment.CurrentDirector is never changed, so it shall be the path of solution.
Eg:
string szProjectPath = System.Environment.CurrentDirectory + #"\where\your\project\is";
I prefer fixed location rather than GetParent().
One drawback of GetParent is when build is changed from AnyCPU to x86, default path would be changed from bin\Debug to bin\x86\Debug.
Need to get another parent, and it's pain in the neck.
Also, you may still access to you test assemblies at TestContext.CurrentContext.TestDirectory and get output from TestContext.CurrentContext.WorkDirectory
Edit:
Note: There are many changes in NUnit3. I will suggest reading through the documentation about "Breaking changes"

The best solution I found was to put the file as an embedded resource on the test project and get it from my unit test. With this solution I don´t need to care about file paths.

I'm not sure if this helps, but this looks to be briefly touched on in the following question.
Visual Studio Solution Path environment variable

In general you may use this, regardless if running a test or console app or web app:
// returns the absolute path of assembly, file://C:/.../MyAssembly.dll
var codeBase = Assembly.GetExecutingAssembly().CodeBase;
// returns the absolute path of assembly, i.e: C:\...\MyAssembly.dll
var location = Assembly.GetExecutingAssembly().Location;
If you are running NUnit, then:
// return the absolute path of directory, i.e. C:\...\
var testDirectory = TestContext.CurrentContext.TestDirectory;

My approach relies on getting the location of the unit testing assembly and then traversing upwards. In the following snippet the variable folderProjectLevel will give you the path to the Unit test project.
string pathAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location;
string folderAssembly = System.IO.Path.GetDirectoryName(pathAssembly);
if (folderAssembly.EndsWith("\\") == false) {
folderAssembly = folderAssembly + "\\";
}
string folderProjectLevel = System.IO.Path.GetFullPath(folderAssembly + "..\\..\\");

You can do it like this:
using System.IO;
Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, #"..\..\"));

use StackTrace
internal static class Extensions
{
public static string GetSourceDirectoryName(this Type type)
{
StackTrace stackTrace = new StackTrace(true);
foreach (var frame in stackTrace.GetFrames())
{
if (frame.GetMethod() is { } method && method.DeclaringType == type)
{
return Path.GetDirectoryName(frame.GetFileName());
}
}
throw new Exception($"未找到{type.Name}源文件目录");
}
}

Related

How to compare file paths from JsonConfigurationSources and Directory.GetFiles properly?

I created an extension method to add all JSON configuration files to the IConfigurationBuilder
public static class IConfigurationBuilderExtensions
{
public static IConfigurationBuilder AddJsonFilesFromDirectory(
this IConfigurationBuilder configurationBuilder,
IFileSystem fileSystem,
string pathToDirectory,
bool fileIsOptional,
bool reloadConfigurationOnFileChange,
string searchPattern = "*.json",
SearchOption directorySearchOption = SearchOption.AllDirectories)
{
var jsonFilePaths = fileSystem.Directory.EnumerateFiles(pathToDirectory, searchPattern, directorySearchOption);
foreach (var jsonFilePath in jsonFilePaths)
{
configurationBuilder.AddJsonFile(jsonFilePath, fileIsOptional, reloadConfigurationOnFileChange);
}
return configurationBuilder;
}
}
and want to create tests for it using xUnit. Based on
How do you mock out the file system in C# for unit testing?
I installed the packages System.IO.Abstractions and System.IO.Abstractions.TestingHelpers and started to test that JSON files from directories have been added
public sealed class IConfigurationBuilderExtensionsTests
{
private const string DirectoryRootPath = "./";
private readonly MockFileSystem _fileSystem;
public IConfigurationBuilderExtensionsTests()
{
_fileSystem = new MockFileSystem(new[]
{
"text.txt",
"config.json",
"dir/foo.json",
"dir/bar.xml",
"dir/sub/deeper/config.json"
}
.Select(filePath => Path.Combine(DirectoryRootPath, filePath))
.ToDictionary(
filePath => filePath,
_ => new MockFileData(string.Empty)));
}
[Theory]
[InlineData("*.json", SearchOption.AllDirectories)]
[InlineData("*.json", SearchOption.TopDirectoryOnly)]
// ... more theories go here ...
public void ItShouldAddJsonFilesFromDirectory(string searchPattern, SearchOption searchOption)
{
var addedJsonFilePaths = new ConfigurationBuilder()
.AddJsonFilesFromDirectory(_fileSystem, DirectoryRootPath, true, true, searchPattern, searchOption)
.Sources
.OfType<JsonConfigurationSource>()
.Select(jsonConfigurationSource => jsonConfigurationSource.Path)
.ToArray();
var jsonFilePathsFromTopDirectory = _fileSystem.Directory.GetFiles(DirectoryRootPath, searchPattern, searchOption);
Assert.True(addedJsonFilePaths.Length == jsonFilePathsFromTopDirectory.Length);
for (int i = 0; i < addedJsonFilePaths.Length; i++)
{
Assert.Equal(
jsonFilePathsFromTopDirectory[i],
Path.DirectorySeparatorChar + addedJsonFilePaths[i]);
}
}
}
The tests are passing but I would like to know if I could get in trouble when prepending Path.DirectorySeparatorChar to addedJsonFilePaths[i].
The problem is that
jsonFilePathsFromTopDirectory[i] returns "/config.json"
addedJsonFilePaths[i] returns "config.json"
so I have to prepend a slash at the beginning. Do you have any suggestions how to improve this / avoid later problems?
/config.json is an absolute path and config.json is a relative path, so to compare them you have to convert the relative path into absolute path by giving it a directory.
But this isn't the real problem, the document is not detailed enough (In fact it doesn't mention about this at all).
When you add a path by AddJsonFile extension method, it will automatically call FileConfigurationSource.ResolveFileProvider.
If no file provider has been set, for absolute Path, this will creates a physical file provider for the nearest existing directory.
This method convert the absolute path into a relative path, that's why /config.json becomes config.json, the directory info is put into an auto-generated file provider.
So to use the API correctly, you need change:
jsonConfigurationSource.Path
to:
jsonConfigurationSource.FileProvider.GetFileInfo(jsonConfigurationSource.Path).PhysicalPath
Or you can provide a FileProvider:
configurationBuilder.AddJsonFile(new NullFileProvider(), jsonFilePath, fileIsOptional, reloadConfigurationOnFileChange);
Instead of adding the directory separator character yourself, you might use System.IO.Path.Combine, which takes cares of that; it only adds one if needed.
Side note: since jsonFilePathsFromTopDirectory[i] returns /config.json having a / instead of a \, you might consider to use Path.AltDirectorySeparatorChar / instead of Path.DirectorySeparatorChar \.
In either way, Path.Combine deals with both.
Both below statements result in /config.json.
Path.Combine(Path.AltDirectorySeparatorChar.ToString(), "/config.json");
Path.Combine(Path.AltDirectorySeparatorChar.ToString(), "config.json");
Your assert statement would look like
Assert.Equal(
jsonFilePathsFromTopDirectory[i],
Path.Combine(Path.AltDirectorySeparatorChar.ToString(), addedJsonFilePaths[i])
);
The logic of comparing files seems alright, I don't find any outstanding problem with it, it is ok to prepend the "/" to match what you need.
Could be even better if you could use the System.IO.Path.DirectorySeparatorChar for the directory root path as well, so if you run on windows or Linux you will have no issues.
But there may be a conceptual problem with what you are doing.
To my understanding you aim to verify existence of specific configuration files required for your program to work right, if those files are missing than the program should fail. But that kind of failure due to missing configuration files, is an expected and valid result of your code.
Yet, you unit-test this as if missing files should fail the test, as if missing files are an indication that something wrong with your code, this is wrong.
Missing files are not indication of your code not working correct and Unit-test should not be used as a validator to make sure the files exist prior executing the program, you will likely agree that unit-test is not part of the actual process and it should only aim to test your code and not preconditions, the test should compare an expected result (mock result of your code) vs. actual result and certainly not meant to become part of the code. That unit test looks like a validator that should be in the code.
So unless those files are produced by your specific code (and not the deployment) there is no sense testing that. In such case you need to create a configuration validator code - and your unit test could test that instead. So it will test that the validator expected result with a mock input you provide. But the thing here is that you would know that you only testing the validation logic and not the actual existence of the files.

Directory disappears after test concludes

I'm trying to put the output of a UnitTest in an intellgent location so I can:
Run the test multiple times and either overwrite or create new results
Find the results / trace / log files I create
Avoid hard coding the path
The advice seems to be to use TestContext (https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.testtools.unittesting.testcontext?view=mstest-net-1.2.0). My unit test correctly populates this object; I can see that as I'm debugging.
I've tried writing to several files and come up with frustrating results.
Write to a file in the Test Directory
string fileOutput = Path.Combine(m_testContext.TestDir, "readmeBasic.txt");
using (System.IO.StreamWriter file = new System.IO.StreamWriter(fileOutput))
{
file.WriteLine("Basic Data");
...
}
Write a trace message
m_testContext.WriteLine("Test Directory " + m_testContext.TestDir);
When I execute my code, I can see the readmeBasic.txt appear. Using Notepad++ during execution of the unit test, the file appears empty even after several WriteLine statements. After successful, error free execution of my unit test, the directory with the file gets removed (by Visual Studio ???).
Is there a configuration I'm missing?
Where is there documentation that explains why this directory gets removed?
What is the point of providing this information in m_testcontext if it is transient and will get removed?
Visual Studio 2019 16.3.9
Yes, the deployment folder will be deleted by default when the test runs
successfully.
If you want to save the folder, you could create a runsettings file, then set “DeleteDeploymentDirectoryAfterTestRunIsComplete” to false, the configuration will retain the deployment folder after a test run.
Please follow this doc to create a runsetting file, then type the below code:
In addition, the ”Testcontext” is used to get some information of running test, such as test name, test outcome and etc. The testcontext.writeline will write into test output, which will input to trx file, so you may need to use file.writeline() to record trace message.
BTW, you could refer this sample about logging current TestMethod with result:
[TestCleanup]
public void LogResult()
{
var testOutcome = TestContext.CurrentTestOutcome;
string testName = TestContext.TestName;
string testdir = Path.Combine(TestContext.TestDir,"log.txt");
string str = testName + ": " + testOutcome.ToString() + "\n";
using (System.IO.StreamWriter file = new System.IO.StreamWriter(testdir))
{
file.WriteLine(str);
file.WriteLine("Basic Data");
}
}
public TestContext TestContext
{
get { return testContextInstance; }
set { testContextInstance = value; }
}
private TestContext testContextInstance;

What are relative paths relative to?

Consider the following statement in a C# console application:
Process.Start("3rdParty/SomeTool.exe");
This statement starts SomeTool.exe in the 3rdParty folder relative to... what, exactly? The folder where the application's .exe resides? The current working directory (which can be changed during the application's lifetime)? Something else?
It is relative to the current working directory of your process.
You can determine your current working directory using Directory.GetCurrentDirectory() and change it using Directory.SetCurrentDirectory().
Well, why don't we find out?
Let's create a simple console application and have some fun with it:
namespace ConsoleApplication1
{
class Program
{
public static void Main()
{
Directory.CreateDirectory("Test");
Console.WriteLine($"Absolute path is: { Path.GetFullPath("Test")}");
Console.ReadLine();
}
}
}
Build it in release mode (we are deploying it after all) and put it in some accesible location. Now double click on it and see what output you get.
Absolute path is: {SomeAccesibleLocationPath}\Test
Hmmm, it seems like the relative path is relative to the directory where the executable was launched. Is this always so?
Let's build another app, we'll call it ConsoleApplication2, and play some more:
class Program
{
public static void Main()
{
Console.WriteLine($"TEST #1: {LaunchProcessAndGetAbsolutePath()}");
Console.WriteLine($"TEST #2: {LaunchProcessAndGetAbsolutePath(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments))}");
Console.ReadLine();
}
private static string LaunchProcessAndGetAbsolutePath(string workingDirectory = null)
{
var startInfo = new ProcessStartInfo(#"{SomeAccesibleLocationPath}\ConsoleApplication1.exe");
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardInput = true;
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
if (workingDirectory != null)
{
startInfo.WorkingDirectory = workingDirectory;
}
using (var p = Process.Start(startInfo))
{
var ret = p.StandardOutput.ReadLine();
p.StandardInput.WriteLine();
p.WaitForExit();
return ret;
}
}
}
If we run this, you'll see that the output is the following:
TEST #1: {MyConsoleApplication2ExecutableDirectory}\Test
TEST #2: {MyDocumentsPath}\Test
Important facts to consider:
The relative paths are always relative to the working directory.
When starting a process from another process, the default working directory is the working directory of the "parent" process, its not the directory of the launched "child" process.
When launching a process by double clicking on the executable, the working directory is set to the executable's directory.
In general, the working directory need not be the executable's directory. Your program's correctness should not rely on this condition ever.
As mentioned, it is relative to the current working directory. The value can be changed and determined via Directory.SetCurrentDirectory(), resp. Directory.GetCurrentDirectory()
Some notes:
The current working directory CAN be the folder where the executable resides; this is the default working directory if none is specified. But it can also be set to a different folder when a process is started or while running. On start either by setting the property ProcessStartInfo.WorkingDirectory when launching a process, or also by using the working directory field in the symbolic link dialog in e.g. the start menu. The latter can be done by a user. The current working directory CAN also be changed by any 3rd party library that is being loaded into the process.
Therefore relative paths in an application without checking the working directory are unreliable and usually cause unexpected behavior.

Environment is not being set in windows using c#. Where am I going wrong?

string path = System.Environment.GetEnvironmentVariable("Path");
Console.WriteLine(path);
if (!path.Contains("C:\ccstg"))
{
if (!path.EndsWith(";"))
path = path + ';';
var v=#"C:\ccstg;";
path = path + v;
Environment.SetEnvironmentVariable("Path",path);
Console.WriteLine(path);
Console.WriteLine("Path Set");
Console.ReadKey();
}
I am trying to set path environment variable using c#, I am able to get "Path" but while setting it is not being set. It doesn't show any error either. I have tried running it as administrator also , no help.
Does anybody what am I missing here ?
Firstly, you need to be a little more careful with your string literals, the code you posted won't compile because "\c" is not a valid string literal escape sequence. To fix:
string newPathComponent = #"C:\ccstg";
if (!path.Contains(newPathComponent))
{
if (!path.EndsWith(";"))
path = path + ';';
path = path + newPathComponent;
Environment.SetEnvironmentVariable("Path", path);
Now, this code works and sets the path for the duration of the process. If you want to set the path permanently, you need to use Environment.SetEnvironmentVariable Method (String, String, EnvironmentVariableTarget), for instance:
var target = EnvironmentVariableTarget.User; // Or EnvironmentVariableTarget.Machine
Environment.SetEnvironmentVariable("Path", path, target);
More here.
However, if you do that, you have to be careful to add your path component only to the path associated with that EnvironmentVariableTarget. That's because the %PATH% environment variable is actually combined from several sources. If you aren't careful, you may copy the combined path into just the EnvironmentVariableTarget.Machine or EnvironmentVariableTarget.User source -- which you do not want to do.
Thus:
static void AddToEnvironmentPath(string pathComponent, EnvironmentVariableTarget target)
{
string targetPath = System.Environment.GetEnvironmentVariable("Path", target) ?? string.Empty;
if (!string.IsNullOrEmpty(targetPath) && !targetPath.EndsWith(";"))
targetPath = targetPath + ';';
targetPath = targetPath + pathComponent;
Environment.SetEnvironmentVariable("Path", targetPath, target);
}
Finally, if you are running inside the Visual Studio Hosting Process for debugging, I have observed that if you use Environment.SetEnvironmentVariable("Path",path, EnvironmentVariableTarget.User), changes to the permanent environment will not be picked up until you exit and restart visual studio. Something weird to do with the visual studio hosting process, I reckon. To handle this odd scenario you might want to do both:
AddToEnvironmentPath(#"C:\ccstg", EnvironmentVariableTarget.User)
AddToEnvironmentPath(#"C:\ccstg", EnvironmentVariableTarget.Process)
SetEnvironmentVariable sets the variable for the current process. Your process has its own environment. When you set an environment variable in your program, it only affects your program's environment.
If you want to affect the user's environment, that is, make the change so that it can be seen outside your program, then you have to call this overload. For example:
Environment.SetEnvironmentVariable("Path", path, EnvironmentVariableTarget.User);
See EnvironmentVariableTarget enumeration for more details.

How to get a ClickOnce application path and version number using C#/.NET code?

I want to get the path and version number of a ClickOnce application, provided the name of the ClickOnce application.
When I manually searched for it, I found it at the path as follows:
'C:\Users\krishnaim\AppData\Local\Apps\2.0\1HCG3KL0.K41\VO5BM4JR.RPO\head..tion_7446cb71d1187222_0005.0037_37dfcf0728461a82\HeadCount.exe'
But this keeps on changing, and it will become a hard-coded path. Is there another way to get a ClickOnce application (for example, HeadCount.exe which is already installed) path and version number using C#/.NET code?
It seems a little bizarre, but getting the current directory of the executing assembly is a bit tricky so my code below may be doing more than you think it should, but I assure you it is mitigating some issues where others may attempt to use Assembly.GetExecutingAssembly.Location property.
static public string AssemblyDirectory
{
get
{
//Don't use Assembly.GetExecutingAssembly().Location, instead use the CodeBase property
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return System.IO.Path.GetDirectoryName(path);
}
}
static public string AssemblyVersion
{
get
{
var asm = Assembly.GetExecutingAssembly();
//If you want the full four-part version number:
return asm.GetName().Version.ToString(4);
//You can reference asm.GetName().Version to get Major, Minor, MajorRevision, MinorRevision
//components individually and do with them as you please.
}
}
In order to do a ClickOnce application update you do not have to do so manually as long as you are using the standard deployment manifests (which I don't know how to ClickOnce unless you do use them).
The MSDN article Choosing a ClickOnce Update Strategy describes the different options for application updates.

Categories

Resources