Skip to content

Commit 60ea2f7

Browse files
authored
Change WriteCodeFragment task to create directory (#8558)
Fixes #8516 Context Setting an OutputDirectory where the directory does not exist, and setting an OutputFile would fail. (Setting an OutputDirectory where the directory does not exist, and not providing an OutputFile value would succeed because FileUtilities.GetTemporaryFile calls Directory.CreateDirectory.) Changes Made WriteCodeFragment_Tests.cs Added new unit tests: CombineFileDirectoryAndDirectoryDoesNotExist (pairs with CombineFileDirectory) ToDirectoryAndDirectoryDoesNotExist (pairs with ToDirectory) Added overload of CreateTask WriteCodeFragment.cs Changed task to call FileUtilities.EnsureDirectoryExists. Testing Added unit tests first and confirmed failure. Ran all unit tests on Windows 11 and macOS 12 (Monterey).
1 parent a93882f commit 60ea2f7

File tree

2 files changed

+85
-10
lines changed

2 files changed

+85
-10
lines changed

src/Tasks.UnitTests/WriteCodeFragment_Tests.cs

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,51 @@ public void CombineFileDirectory()
9090
string file = Path.Combine(Path.GetTempPath(), "CombineFileDirectory.tmp");
9191
Assert.Equal(file, task.OutputFile.ItemSpec);
9292
Assert.True(File.Exists(file));
93+
94+
File.Delete(task.OutputFile.ItemSpec);
95+
}
96+
97+
/// <summary>
98+
/// Combine file and directory where the directory does not already exist
99+
/// </summary>
100+
[Fact]
101+
public void CombineFileDirectoryAndDirectoryDoesNotExist()
102+
{
103+
using TestEnvironment env = TestEnvironment.Create();
104+
105+
TaskItem folder = new TaskItem(env.CreateFolder(folderPath: null, createFolder: false).Path);
106+
107+
TaskItem file = new TaskItem("CombineFileDirectory.tmp");
108+
109+
string expectedFile = Path.Combine(folder.ItemSpec, file.ItemSpec);
110+
WriteCodeFragment task = CreateTask("c#", folder, file, new TaskItem[] { new TaskItem("aa") });
111+
MockEngine engine = new MockEngine(true);
112+
task.BuildEngine = engine;
113+
bool result = task.Execute();
114+
115+
Assert.True(result);
116+
Assert.Equal(expectedFile, task.OutputFile.ItemSpec);
117+
Assert.True(File.Exists(expectedFile));
118+
}
119+
120+
/// <summary>
121+
/// Combine file and directory where the directory does not already exist
122+
/// </summary>
123+
[Fact]
124+
public void FileWithPathAndDirectoryDoesNotExist()
125+
{
126+
using TestEnvironment env = TestEnvironment.Create();
127+
128+
TaskItem file = new TaskItem(Path.Combine(env.CreateFolder(folderPath: null, createFolder: false).Path, "File.tmp"));
129+
130+
WriteCodeFragment task = CreateTask("c#", null, file, new TaskItem[] { new TaskItem("aa") });
131+
MockEngine engine = new MockEngine(true);
132+
task.BuildEngine = engine;
133+
bool result = task.Execute();
134+
135+
Assert.True(result);
136+
Assert.Equal(file.ItemSpec, task.OutputFile.ItemSpec);
137+
Assert.True(File.Exists(task.OutputFile.ItemSpec));
93138
}
94139

95140
/// <summary>
@@ -176,7 +221,7 @@ public void NoAttributesShouldEmitNoFile2()
176221
/// <summary>
177222
/// Bad file path
178223
/// </summary>
179-
[Fact]
224+
[WindowsOnlyFact(additionalMessage: "No invalid characters on Unix.")]
180225
public void InvalidFilePath()
181226
{
182227
WriteCodeFragment task = new WriteCodeFragment();
@@ -317,6 +362,27 @@ public void ToDirectory()
317362
File.Delete(task.OutputFile.ItemSpec);
318363
}
319364

365+
/// <summary>
366+
/// Specify directory where the directory does not already exist
367+
/// </summary>
368+
[Fact]
369+
public void ToDirectoryAndDirectoryDoesNotExist()
370+
{
371+
using TestEnvironment env = TestEnvironment.Create();
372+
373+
TaskItem folder = new TaskItem(env.CreateFolder(folderPath: null, createFolder: false).Path);
374+
375+
WriteCodeFragment task = CreateTask("c#", folder, null, new TaskItem[] { new TaskItem("System.AssemblyTrademarkAttribute") });
376+
MockEngine engine = new MockEngine(true);
377+
task.BuildEngine = engine;
378+
bool result = task.Execute();
379+
380+
Assert.True(result);
381+
Assert.True(File.Exists(task.OutputFile.ItemSpec));
382+
Assert.Equal(folder.ItemSpec, task.OutputFile.ItemSpec.Substring(0, folder.ItemSpec.Length));
383+
Assert.Equal(".cs", task.OutputFile.ItemSpec.Substring(task.OutputFile.ItemSpec.Length - 3));
384+
}
385+
320386
/// <summary>
321387
/// Regular case
322388
/// </summary>
@@ -874,7 +940,7 @@ public void InferredTypeForNamedParameter()
874940
}
875941

876942
/// <summary>
877-
/// For backward-compatibility, if multiple constructors are found with the same number
943+
/// For backward-compatibility, if multiple constructors are found with the same number
878944
/// of position arguments that was specified in the metadata, then the constructor that
879945
/// has strings for every parameter should be used.
880946
/// </summary>
@@ -985,11 +1051,18 @@ public void UsingInferredDeclaredTypesAndLiteralsInSameAttribute()
9851051

9861052
private WriteCodeFragment CreateTask(string language, params TaskItem[] attributes)
9871053
{
988-
WriteCodeFragment task = new();
989-
task.Language = language;
990-
task.OutputDirectory = new TaskItem(Path.GetTempPath());
991-
task.AssemblyAttributes = attributes;
992-
return task;
1054+
return CreateTask(language, new TaskItem(Path.GetTempPath()), null, attributes);
1055+
}
1056+
1057+
private WriteCodeFragment CreateTask(string language, TaskItem outputDirectory, TaskItem outputFile, params TaskItem[] attributes)
1058+
{
1059+
return new WriteCodeFragment()
1060+
{
1061+
Language = language,
1062+
OutputDirectory = outputDirectory,
1063+
OutputFile = outputFile,
1064+
AssemblyAttributes = attributes
1065+
};
9931066
}
9941067

9951068
private void ExecuteAndVerifySuccess(WriteCodeFragment task, params string[] expectedAttributes)

src/Tasks/WriteCodeFragment.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public class WriteCodeFragment : TaskExtension
6767
/// The path to the file that was generated.
6868
/// If this is set, and a file name, the destination folder will be prepended.
6969
/// If this is set, and is rooted, the destination folder will be ignored.
70-
/// If this is not set, the destination folder will be used, an arbitrary file name will be used, and
70+
/// If this is not set, the destination folder will be used, an arbitrary file name will be used, and
7171
/// the default extension for the language selected.
7272
/// </summary>
7373
[Output]
@@ -113,6 +113,8 @@ public override bool Execute()
113113

114114
OutputFile ??= new TaskItem(FileUtilities.GetTemporaryFile(OutputDirectory.ItemSpec, null, extension));
115115

116+
FileUtilities.EnsureDirectoryExists(Path.GetDirectoryName(OutputFile.ItemSpec));
117+
116118
File.WriteAllText(OutputFile.ItemSpec, code); // Overwrites file if it already exists (and can be overwritten)
117119
}
118120
catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex))
@@ -481,7 +483,7 @@ private Type[] FindPositionalParameterTypes(Type attributeType, IReadOnlyList<At
481483
Log.LogMessageFromResources("WriteCodeFragment.MultipleConstructorsFound");
482484

483485
// Before parameter types could be specified, all parameter values were
484-
// treated as strings. To be backward-compatible, we need to prefer
486+
// treated as strings. To be backward-compatible, we need to prefer
485487
// the constructor that has all string parameters, if it exists.
486488
var allStringParameters = candidates.FirstOrDefault(c => c.All(t => t == typeof(string)));
487489

@@ -551,7 +553,7 @@ private bool TryConvertParameterValue(string typeName, string rawValue, out Code
551553
/// </summary>
552554
private CodeExpression ConvertParameterValueToInferredType(Type inferredType, string rawValue, string parameterName)
553555
{
554-
// If we don't know what type the parameter should be, then we
556+
// If we don't know what type the parameter should be, then we
555557
// can't convert the type. We'll just treat is as a string.
556558
if (inferredType is null)
557559
{

0 commit comments

Comments
 (0)