/// <summary> /// Run a Torch unit test. /// </summary> /// <param name="element">The unit test element.</param> /// <param name="previousUnitResults">The previous unit test results.</param> /// <param name="update">Indicates whether to give the user the option to fix a failing test.</param> /// <param name="args">The command arguments passed to WixUnit.</param> public static void RunUnitTest(XmlElement element, UnitResults previousUnitResults, bool update, ICommandArgs args) { string arguments = element.GetAttribute("Arguments"); string expectedErrors = element.GetAttribute("ExpectedErrors"); string expectedWarnings = element.GetAttribute("ExpectedWarnings"); string extensions = element.GetAttribute("Extensions"); string outputFile = element.GetAttribute("OutputFile"); string targetDatabase = element.GetAttribute("TargetDatabase"); string tempDirectory = element.GetAttribute("TempDirectory"); string testName = element.ParentNode.Attributes["Name"].Value; string toolsDirectory = element.GetAttribute("ToolsDirectory"); string updatedDatabase = element.GetAttribute("UpdatedDatabase"); bool usePreviousOutput = ("true" == element.GetAttribute("UsePreviousOutput")); bool verifyTransform = ("true" == element.GetAttribute("VerifyTransform")); string toolFile = Path.Combine(toolsDirectory, "torch.exe"); StringBuilder commandLine = new StringBuilder(arguments); // handle extensions if (!String.IsNullOrEmpty(extensions)) { foreach (string extension in extensions.Split(';')) { commandLine.AppendFormat(" -ext \"{0}\"", extension); } } // handle wixunit arguments if (args.NoTidy) { commandLine.Append(" -notidy"); } // handle any previous outputs if (0 < previousUnitResults.OutputFiles.Count && usePreviousOutput) { commandLine.AppendFormat(" \"{0}\"", previousUnitResults.OutputFiles[0]); previousUnitResults.OutputFiles.Clear(); } else // diff database files to create transform { commandLine.AppendFormat(" \"{0}\" \"{1}\"", targetDatabase, updatedDatabase); } if (null == outputFile || String.Empty == outputFile) { outputFile = Path.Combine(tempDirectory, "transform.mst"); } commandLine.AppendFormat(" -out \"{0}\"", outputFile); previousUnitResults.OutputFiles.Add(outputFile); // run the tool ArrayList output = ToolUtility.RunTool(toolFile, commandLine.ToString()); previousUnitResults.Errors.AddRange(ToolUtility.GetErrors(output, expectedErrors, expectedWarnings)); previousUnitResults.Output.AddRange(output); // check the results if (verifyTransform && 0 == expectedErrors.Length && 0 == previousUnitResults.Errors.Count) { string actualDatabase = Path.Combine(tempDirectory, String.Concat(Guid.NewGuid(), ".msi")); File.Copy(targetDatabase, actualDatabase); File.SetAttributes(actualDatabase, File.GetAttributes(actualDatabase) & ~FileAttributes.ReadOnly); using (Database database = new Database(actualDatabase, OpenDatabase.Direct)) { // use transform validation bits set in the transform (if any; defaults to None). database.ApplyTransform(outputFile); database.Commit(); } // check the output file ArrayList differences = CompareUnit.CompareResults(updatedDatabase, actualDatabase, testName, update); previousUnitResults.Errors.AddRange(differences); previousUnitResults.Output.AddRange(differences); } }
/// <summary> /// Unbind an MSI transform file. /// </summary> /// <param name="transformFile">The transform file.</param> /// <param name="exportBasePath">The path where files should be exported.</param> /// <returns>The unbound transform.</returns> private Output UnbindTransform(string transformFile, string exportBasePath) { Output transform = new Output(SourceLineNumberCollection.FromFileName(transformFile)); transform.Type = OutputType.Transform; // get the summary information table using (SummaryInformation summaryInformation = new SummaryInformation(transformFile)) { Table table = transform.Tables.EnsureTable(null, this.tableDefinitions["_SummaryInformation"]); for (int i = 1; 19 >= i; i++) { string value = summaryInformation.GetProperty(i); if (0 < value.Length) { Row row = table.CreateRow(transform.SourceLineNumbers); row[0] = i; row[1] = value; } } } // create a schema msi which hopefully matches the table schemas in the transform Output schemaOutput = new Output(null); string msiDatabaseFile = Path.Combine(this.tempFiles.BasePath, "schema.msi"); foreach (TableDefinition tableDefinition in this.tableDefinitions) { // skip unreal tables and the Patch table if (!tableDefinition.IsUnreal && "Patch" != tableDefinition.Name) { schemaOutput.EnsureTable(tableDefinition); } } Hashtable addedRows = new Hashtable(); Table transformViewTable; // bind the schema msi using (Binder binder = new Binder()) { binder.SuppressAddingValidationRows = true; binder.WixVariableResolver = new WixVariableResolver(); binder.GenerateDatabase(schemaOutput, msiDatabaseFile, true, false); // apply the transform to the database and retrieve the modifications using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) { // apply the transform with the ViewTransform option to collect all the modifications msiDatabase.ApplyTransform(transformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform); // unbind the database Output transformViewOutput = this.UnbindDatabase(msiDatabaseFile, msiDatabase, OutputType.Product, exportBasePath, true); // index the added and possibly modified rows (added rows may also appears as modified rows) transformViewTable = transformViewOutput.Tables["_TransformView"]; Hashtable modifiedRows = new Hashtable(); foreach (Row row in transformViewTable.Rows) { string tableName = (string) row[0]; string columnName = (string) row[1]; string primaryKeys = (string) row[2]; if ("INSERT" == columnName) { string index = String.Concat(tableName, ':', primaryKeys); addedRows.Add(index, null); } else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row { string index = String.Concat(tableName, ':', primaryKeys); modifiedRows[index] = row; } } // create placeholder rows for modified rows to make the transform insert the updated values when its applied foreach (Row row in modifiedRows.Values) { string tableName = (string) row[0]; string columnName = (string) row[1]; string primaryKeys = (string) row[2]; string index = String.Concat(tableName, ':', primaryKeys); // ignore information for added rows if (!addedRows.Contains(index)) { Table table = schemaOutput.Tables[tableName]; this.CreateRow(table, primaryKeys, true); } } } // re-bind the schema output with the placeholder rows binder.GenerateDatabase(schemaOutput, msiDatabaseFile, true, false); } // apply the transform to the database and retrieve the modifications using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) { try { // apply the transform msiDatabase.ApplyTransform(transformFile, TransformErrorConditions.All); // commit the database to guard against weird errors with streams msiDatabase.Commit(); } catch (Win32Exception ex) { if (0x65B == ex.NativeErrorCode) { // this commonly happens when the transform was built // against a database schema different from the internal // table definitions throw new WixException(WixErrors.TransformSchemaMismatch()); } } // unbind the database Output output = this.UnbindDatabase(msiDatabaseFile, msiDatabase, OutputType.Product, exportBasePath, true); // index all the rows to easily find modified rows Hashtable rows = new Hashtable(); foreach (Table table in output.Tables) { foreach (Row row in table.Rows) { rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row); } } // process the _TransformView rows into transform rows foreach (Row row in transformViewTable.Rows) { string tableName = (string)row[0]; string columnName = (string)row[1]; string primaryKeys = (string)row[2]; Table table = transform.Tables.EnsureTable(null, this.tableDefinitions[tableName]); if ("CREATE" == columnName) // added table { table.Operation = TableOperation.Add; } else if ("DELETE" == columnName) // deleted row { Row deletedRow = this.CreateRow(table, primaryKeys, false); deletedRow.Operation = RowOperation.Delete; } else if ("DROP" == columnName) // dropped table { table.Operation = TableOperation.Drop; } else if ("INSERT" == columnName) // added row { string index = String.Concat(tableName, ':', primaryKeys); Row addedRow = (Row)rows[index]; addedRow.Operation = RowOperation.Add; table.Rows.Add(addedRow); } else if (null != primaryKeys) // modified row { string index = String.Concat(tableName, ':', primaryKeys); // the _TransformView table includes information for added rows // that looks like modified rows so it sometimes needs to be ignored if (!addedRows.Contains(index)) { Row modifiedRow = (Row)rows[index]; // mark the field as modified int indexOfModifiedValue = modifiedRow.TableDefinition.Columns.IndexOf(columnName); modifiedRow.Fields[indexOfModifiedValue].Modified = true; // move the modified row into the transform the first time its encountered if (RowOperation.None == modifiedRow.Operation) { modifiedRow.Operation = RowOperation.Modify; table.Rows.Add(modifiedRow); } } } else // added column { table.Definition.Columns[columnName].Added = true; } } } return transform; }