|
| 1 | +using Xunit; |
| 2 | +using Semmle.Autobuild.Shared; |
| 3 | +using System.Collections.Generic; |
| 4 | +using System; |
| 5 | +using System.Linq; |
| 6 | +using Microsoft.Build.Construction; |
| 7 | +using System.Xml; |
| 8 | + |
| 9 | +namespace Semmle.Autobuild.Cpp.Tests |
| 10 | +{ |
| 11 | + /// <summary> |
| 12 | + /// Test class to script Autobuilder scenarios. |
| 13 | + /// For most methods, it uses two fields: |
| 14 | + /// - an IList to capture the the arguments passed to it |
| 15 | + /// - an IDictionary of possible return values. |
| 16 | + /// </summary> |
| 17 | + class TestActions : IBuildActions |
| 18 | + { |
| 19 | + /// <summary> |
| 20 | + /// List of strings passed to FileDelete. |
| 21 | + /// </summary> |
| 22 | + public IList<string> FileDeleteIn = new List<string>(); |
| 23 | + |
| 24 | + void IBuildActions.FileDelete(string file) |
| 25 | + { |
| 26 | + FileDeleteIn.Add(file); |
| 27 | + } |
| 28 | + |
| 29 | + public IList<string> FileExistsIn = new List<string>(); |
| 30 | + public IDictionary<string, bool> FileExists = new Dictionary<string, bool>(); |
| 31 | + |
| 32 | + bool IBuildActions.FileExists(string file) |
| 33 | + { |
| 34 | + FileExistsIn.Add(file); |
| 35 | + if (FileExists.TryGetValue(file, out var ret)) |
| 36 | + return ret; |
| 37 | + if (FileExists.TryGetValue(System.IO.Path.GetFileName(file), out ret)) |
| 38 | + return ret; |
| 39 | + throw new ArgumentException("Missing FileExists " + file); |
| 40 | + } |
| 41 | + |
| 42 | + public IList<string> RunProcessIn = new List<string>(); |
| 43 | + public IDictionary<string, int> RunProcess = new Dictionary<string, int>(); |
| 44 | + public IDictionary<string, string> RunProcessOut = new Dictionary<string, string>(); |
| 45 | + public IDictionary<string, string> RunProcessWorkingDirectory = new Dictionary<string, string>(); |
| 46 | + |
| 47 | + int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, IDictionary<string, string>? env, out IList<string> stdOut) |
| 48 | + { |
| 49 | + var pattern = cmd + " " + args; |
| 50 | + RunProcessIn.Add(pattern); |
| 51 | + if (RunProcessOut.TryGetValue(pattern, out var str)) |
| 52 | + stdOut = str.Split("\n"); |
| 53 | + else |
| 54 | + throw new ArgumentException("Missing RunProcessOut " + pattern); |
| 55 | + RunProcessWorkingDirectory.TryGetValue(pattern, out var wd); |
| 56 | + if (wd != workingDirectory) |
| 57 | + throw new ArgumentException("Missing RunProcessWorkingDirectory " + pattern); |
| 58 | + if (RunProcess.TryGetValue(pattern, out var ret)) |
| 59 | + return ret; |
| 60 | + throw new ArgumentException("Missing RunProcess " + pattern); |
| 61 | + } |
| 62 | + |
| 63 | + int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, IDictionary<string, string>? env) |
| 64 | + { |
| 65 | + var pattern = cmd + " " + args; |
| 66 | + RunProcessIn.Add(pattern); |
| 67 | + RunProcessWorkingDirectory.TryGetValue(pattern, out var wd); |
| 68 | + if (wd != workingDirectory) |
| 69 | + throw new ArgumentException("Missing RunProcessWorkingDirectory " + pattern); |
| 70 | + if (RunProcess.TryGetValue(pattern, out var ret)) |
| 71 | + return ret; |
| 72 | + throw new ArgumentException("Missing RunProcess " + pattern); |
| 73 | + } |
| 74 | + |
| 75 | + public IList<string> DirectoryDeleteIn = new List<string>(); |
| 76 | + |
| 77 | + void IBuildActions.DirectoryDelete(string dir, bool recursive) |
| 78 | + { |
| 79 | + DirectoryDeleteIn.Add(dir); |
| 80 | + } |
| 81 | + |
| 82 | + public IDictionary<string, bool> DirectoryExists = new Dictionary<string, bool>(); |
| 83 | + public IList<string> DirectoryExistsIn = new List<string>(); |
| 84 | + |
| 85 | + bool IBuildActions.DirectoryExists(string dir) |
| 86 | + { |
| 87 | + DirectoryExistsIn.Add(dir); |
| 88 | + if (DirectoryExists.TryGetValue(dir, out var ret)) |
| 89 | + return ret; |
| 90 | + throw new ArgumentException("Missing DirectoryExists " + dir); |
| 91 | + } |
| 92 | + |
| 93 | + public IDictionary<string, string?> GetEnvironmentVariable = new Dictionary<string, string?>(); |
| 94 | + |
| 95 | + string? IBuildActions.GetEnvironmentVariable(string name) |
| 96 | + { |
| 97 | + if (GetEnvironmentVariable.TryGetValue(name, out var ret)) |
| 98 | + return ret; |
| 99 | + throw new ArgumentException("Missing GetEnvironmentVariable " + name); |
| 100 | + } |
| 101 | + |
| 102 | + public string GetCurrentDirectory = ""; |
| 103 | + |
| 104 | + string IBuildActions.GetCurrentDirectory() |
| 105 | + { |
| 106 | + return GetCurrentDirectory; |
| 107 | + } |
| 108 | + |
| 109 | + public IDictionary<string, string> EnumerateFiles = new Dictionary<string, string>(); |
| 110 | + |
| 111 | + IEnumerable<string> IBuildActions.EnumerateFiles(string dir) |
| 112 | + { |
| 113 | + if (EnumerateFiles.TryGetValue(dir, out var str)) |
| 114 | + return str.Split("\n"); |
| 115 | + throw new ArgumentException("Missing EnumerateFiles " + dir); |
| 116 | + } |
| 117 | + |
| 118 | + public IDictionary<string, string> EnumerateDirectories = new Dictionary<string, string>(); |
| 119 | + |
| 120 | + IEnumerable<string> IBuildActions.EnumerateDirectories(string dir) |
| 121 | + { |
| 122 | + if (EnumerateDirectories.TryGetValue(dir, out var str)) |
| 123 | + return string.IsNullOrEmpty(str) ? Enumerable.Empty<string>() : str.Split("\n"); |
| 124 | + throw new ArgumentException("Missing EnumerateDirectories " + dir); |
| 125 | + } |
| 126 | + |
| 127 | + public bool IsWindows; |
| 128 | + |
| 129 | + bool IBuildActions.IsWindows() => IsWindows; |
| 130 | + |
| 131 | + string IBuildActions.PathCombine(params string[] parts) |
| 132 | + { |
| 133 | + return string.Join(IsWindows ? '\\' : '/', parts.Where(p => !string.IsNullOrWhiteSpace(p))); |
| 134 | + } |
| 135 | + |
| 136 | + string IBuildActions.GetFullPath(string path) => path; |
| 137 | + |
| 138 | + void IBuildActions.WriteAllText(string filename, string contents) |
| 139 | + { |
| 140 | + } |
| 141 | + |
| 142 | + public IDictionary<string, XmlDocument> LoadXml = new Dictionary<string, XmlDocument>(); |
| 143 | + XmlDocument IBuildActions.LoadXml(string filename) |
| 144 | + { |
| 145 | + if (LoadXml.TryGetValue(filename, out var xml)) |
| 146 | + return xml; |
| 147 | + throw new ArgumentException("Missing LoadXml " + filename); |
| 148 | + } |
| 149 | + |
| 150 | + public string EnvironmentExpandEnvironmentVariables(string s) |
| 151 | + { |
| 152 | + foreach (var kvp in GetEnvironmentVariable) |
| 153 | + s = s.Replace($"%{kvp.Key}%", kvp.Value); |
| 154 | + return s; |
| 155 | + } |
| 156 | + } |
| 157 | + |
| 158 | + /// <summary> |
| 159 | + /// A fake solution to build. |
| 160 | + /// </summary> |
| 161 | + class TestSolution : ISolution |
| 162 | + { |
| 163 | + public IEnumerable<SolutionConfigurationInSolution> Configurations => throw new NotImplementedException(); |
| 164 | + |
| 165 | + public string DefaultConfigurationName => "Release"; |
| 166 | + |
| 167 | + public string DefaultPlatformName => "x86"; |
| 168 | + |
| 169 | + public string FullPath { get; set; } |
| 170 | + |
| 171 | + public Version ToolsVersion => new Version("14.0"); |
| 172 | + |
| 173 | + public IEnumerable<IProjectOrSolution> IncludedProjects => throw new NotImplementedException(); |
| 174 | + |
| 175 | + public TestSolution(string path) |
| 176 | + { |
| 177 | + FullPath = path; |
| 178 | + } |
| 179 | + } |
| 180 | + |
| 181 | + public class BuildScriptTests |
| 182 | + { |
| 183 | + TestActions Actions = new TestActions(); |
| 184 | + |
| 185 | + // Records the arguments passed to StartCallback. |
| 186 | + IList<string> StartCallbackIn = new List<string>(); |
| 187 | + |
| 188 | + void StartCallback(string s, bool silent) |
| 189 | + { |
| 190 | + StartCallbackIn.Add(s); |
| 191 | + } |
| 192 | + |
| 193 | + // Records the arguments passed to EndCallback |
| 194 | + IList<string> EndCallbackIn = new List<string>(); |
| 195 | + IList<int> EndCallbackReturn = new List<int>(); |
| 196 | + |
| 197 | + void EndCallback(int ret, string s, bool silent) |
| 198 | + { |
| 199 | + EndCallbackReturn.Add(ret); |
| 200 | + EndCallbackIn.Add(s); |
| 201 | + } |
| 202 | + |
| 203 | + CppAutobuilder CreateAutoBuilder(bool isWindows, |
| 204 | + string? buildless = null, string? solution = null, string? buildCommand = null, string? ignoreErrors = null, |
| 205 | + string? msBuildArguments = null, string? msBuildPlatform = null, string? msBuildConfiguration = null, string? msBuildTarget = null, |
| 206 | + string? dotnetArguments = null, string? dotnetVersion = null, string? vsToolsVersion = null, |
| 207 | + string? nugetRestore = null, string? allSolutions = null, |
| 208 | + string cwd = @"C:\Project") |
| 209 | + { |
| 210 | + string codeqlUpperLanguage = Language.Cpp.UpperCaseName; |
| 211 | + Actions.GetEnvironmentVariable[$"CODEQL_AUTOBUILDER_{codeqlUpperLanguage}_NO_INDEXING"] = "false"; |
| 212 | + Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_TRAP_DIR"] = ""; |
| 213 | + Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_SOURCE_ARCHIVE_DIR"] = ""; |
| 214 | + Actions.GetEnvironmentVariable[$"CODEQL_EXTRACTOR_{codeqlUpperLanguage}_ROOT"] = $@"C:\codeql\{codeqlUpperLanguage.ToLowerInvariant()}"; |
| 215 | + Actions.GetEnvironmentVariable["CODEQL_JAVA_HOME"] = @"C:\codeql\tools\java"; |
| 216 | + Actions.GetEnvironmentVariable["SEMMLE_DIST"] = @"C:\odasa"; |
| 217 | + Actions.GetEnvironmentVariable["SEMMLE_JAVA_HOME"] = @"C:\odasa\tools\java"; |
| 218 | + Actions.GetEnvironmentVariable["SEMMLE_PLATFORM_TOOLS"] = @"C:\odasa\tools"; |
| 219 | + Actions.GetEnvironmentVariable["LGTM_INDEX_VSTOOLS_VERSION"] = vsToolsVersion; |
| 220 | + Actions.GetEnvironmentVariable["LGTM_INDEX_MSBUILD_ARGUMENTS"] = msBuildArguments; |
| 221 | + Actions.GetEnvironmentVariable["LGTM_INDEX_MSBUILD_PLATFORM"] = msBuildPlatform; |
| 222 | + Actions.GetEnvironmentVariable["LGTM_INDEX_MSBUILD_CONFIGURATION"] = msBuildConfiguration; |
| 223 | + Actions.GetEnvironmentVariable["LGTM_INDEX_MSBUILD_TARGET"] = msBuildTarget; |
| 224 | + Actions.GetEnvironmentVariable["LGTM_INDEX_DOTNET_ARGUMENTS"] = dotnetArguments; |
| 225 | + Actions.GetEnvironmentVariable["LGTM_INDEX_DOTNET_VERSION"] = dotnetVersion; |
| 226 | + Actions.GetEnvironmentVariable["LGTM_INDEX_BUILD_COMMAND"] = buildCommand; |
| 227 | + Actions.GetEnvironmentVariable["LGTM_INDEX_SOLUTION"] = solution; |
| 228 | + Actions.GetEnvironmentVariable["LGTM_INDEX_IGNORE_ERRORS"] = ignoreErrors; |
| 229 | + Actions.GetEnvironmentVariable["LGTM_INDEX_BUILDLESS"] = buildless; |
| 230 | + Actions.GetEnvironmentVariable["LGTM_INDEX_ALL_SOLUTIONS"] = allSolutions; |
| 231 | + Actions.GetEnvironmentVariable["LGTM_INDEX_NUGET_RESTORE"] = nugetRestore; |
| 232 | + Actions.GetEnvironmentVariable["ProgramFiles(x86)"] = isWindows ? @"C:\Program Files (x86)" : null; |
| 233 | + Actions.GetCurrentDirectory = cwd; |
| 234 | + Actions.IsWindows = isWindows; |
| 235 | + |
| 236 | + var options = new AutobuildOptions(Actions, Language.Cpp); |
| 237 | + return new CppAutobuilder(Actions, options); |
| 238 | + } |
| 239 | + |
| 240 | + void TestAutobuilderScript(Autobuilder autobuilder, int expectedOutput, int commandsRun) |
| 241 | + { |
| 242 | + Assert.Equal(expectedOutput, autobuilder.GetBuildScript().Run(Actions, StartCallback, EndCallback)); |
| 243 | + |
| 244 | + // Check expected commands actually ran |
| 245 | + Assert.Equal(commandsRun, StartCallbackIn.Count); |
| 246 | + Assert.Equal(commandsRun, EndCallbackIn.Count); |
| 247 | + Assert.Equal(commandsRun, EndCallbackReturn.Count); |
| 248 | + |
| 249 | + var action = Actions.RunProcess.GetEnumerator(); |
| 250 | + for (int cmd = 0; cmd < commandsRun; ++cmd) |
| 251 | + { |
| 252 | + Assert.True(action.MoveNext()); |
| 253 | + |
| 254 | + Assert.Equal(action.Current.Key, StartCallbackIn[cmd]); |
| 255 | + Assert.Equal(action.Current.Value, EndCallbackReturn[cmd]); |
| 256 | + } |
| 257 | + } |
| 258 | + |
| 259 | + |
| 260 | + [Fact] |
| 261 | + public void TestDefaultCppAutobuilder() |
| 262 | + { |
| 263 | + Actions.EnumerateFiles[@"C:\Project"] = ""; |
| 264 | + Actions.EnumerateDirectories[@"C:\Project"] = ""; |
| 265 | + |
| 266 | + var autobuilder = CreateAutoBuilder(true); |
| 267 | + var script = autobuilder.GetBuildScript(); |
| 268 | + |
| 269 | + // Fails due to no solutions present. |
| 270 | + Assert.NotEqual(0, script.Run(Actions, StartCallback, EndCallback)); |
| 271 | + } |
| 272 | + |
| 273 | + [Fact] |
| 274 | + public void TestCppAutobuilderSuccess() |
| 275 | + { |
| 276 | + Actions.RunProcess[@"cmd.exe /C C:\odasa\tools\csharp\nuget\nuget.exe restore C:\Project\test.sln"] = 1; |
| 277 | + Actions.RunProcess[@"cmd.exe /C CALL ^""C:\Program Files ^(x86^)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat^"" && set Platform=&& type NUL && C:\odasa\tools\odasa index --auto msbuild C:\Project\test.sln /p:UseSharedCompilation=false /t:rebuild /p:Platform=""x86"" /p:Configuration=""Release"" /p:MvcBuildViews=true"] = 0; |
| 278 | + Actions.RunProcessOut[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationPath"] = ""; |
| 279 | + Actions.RunProcess[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationPath"] = 1; |
| 280 | + Actions.RunProcess[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationVersion"] = 0; |
| 281 | + Actions.RunProcessOut[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe -prerelease -legacy -property installationVersion"] = ""; |
| 282 | + Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"] = true; |
| 283 | + Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat"] = true; |
| 284 | + Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat"] = true; |
| 285 | + Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"] = true; |
| 286 | + Actions.FileExists[@"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"] = true; |
| 287 | + Actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.slx"; |
| 288 | + Actions.EnumerateDirectories[@"C:\Project"] = ""; |
| 289 | + |
| 290 | + var autobuilder = CreateAutoBuilder(true); |
| 291 | + var solution = new TestSolution(@"C:\Project\test.sln"); |
| 292 | + autobuilder.ProjectsOrSolutionsToBuild.Add(solution); |
| 293 | + TestAutobuilderScript(autobuilder, 0, 2); |
| 294 | + } |
| 295 | + } |
| 296 | +} |
0 commit comments