diff --git a/CI/Azure-DevOps/AZ_MainPipeline.yml b/CI/Azure-DevOps/AZ_MainPipeline.yml index 6319c306d..10dae77ca 100644 --- a/CI/Azure-DevOps/AZ_MainPipeline.yml +++ b/CI/Azure-DevOps/AZ_MainPipeline.yml @@ -88,6 +88,8 @@ stages: inputs: azureSubscription: 'Azure DevOps Main Pipeline Service Principal' KeyVaultName: 'tSQLtSigningKey' + SecretsFilter: '*' + RunAsPreJob: false - task: PowerShell@2 name: CreateResourceGroupName diff --git a/Experiments/CollationTests.sql b/Experiments/CollationTests.sql new file mode 100644 index 000000000..57f615de3 --- /dev/null +++ b/Experiments/CollationTests.sql @@ -0,0 +1,37 @@ +/*-- +RELEVANT LINKS +https://www.red-gate.com/simple-talk/databases/sql-server/t-sql-programming-sql-server/questions-sql-server-collations-shy-ask/ +https://docs.microsoft.com/en-us/sql/t-sql/statements/windows-collation-name-transact-sql?view=sql-server-ver15 +--*/ + +SELECT DATABASEPROPERTYEX('tempdb','Collation') [tempdb collation], + DATABASEPROPERTYEX(DB_NAME(),'Collation') [database collation] +GO + +DROP TABLE IF EXISTS dbo.atable; +DROP TABLE IF EXISTS tempdb..#Actual; +SELECT + 'Hello World!' COLLATE SQL_Polish_CP1250_CI_AS c1, + 'Hello World!' COLLATE SQL_Latin1_General_CP437_BIN c2, + 'Hello World!' COLLATE Albanian_BIN2 c3 + INTO dbo.atable +SELECT * INTO #Actual FROM dbo.atable + +SELECT 'dbo.atable' [table],* FROM sys.columns WHERE object_id = OBJECT_ID('dbo.atable') + +SELECT '#Actual',* FROM tempdb.sys.columns WHERE object_id = OBJECT_ID('tempdb..#Actual'); + +GO + +DROP TABLE IF EXISTS tempdb..#E; +CREATE TABLE #E( + C1 NVARCHAR(MAX) COLLATE DATABASE_DEFAULT, + C2 NVARCHAR(MAX) +); + +SELECT '#E',* FROM tempdb.sys.columns WHERE object_id = OBJECT_ID('tempdb..#E'); +GO +SELECT * +FROM #E E1 +JOIN #E E2 +ON E2.C2 = E1.C1 \ No newline at end of file diff --git a/Experiments/Debugging tSQLt.tdf b/Experiments/Debugging tSQLt.tdf new file mode 100644 index 000000000..86a69c6de Binary files /dev/null and b/Experiments/Debugging tSQLt.tdf differ diff --git a/Experiments/Experiments.ssmssqlproj b/Experiments/Experiments.ssmssqlproj index 4bf7c06d0..7ea928e3d 100644 --- a/Experiments/Experiments.ssmssqlproj +++ b/Experiments/Experiments.ssmssqlproj @@ -36,6 +36,12 @@ CertificateTest.sql + + + + + CollationTests.sql + @@ -60,6 +66,12 @@ LIKE4000.sql + + + + + MSSQL Defect Try-Catch Invalidates Transaction.sql + @@ -102,6 +114,12 @@ SQLCmdTest.1.sql + + + + + TestingTransactionNames.sql + diff --git a/Experiments/MSSQL Defect Try-Catch Invalidates Transaction.sql b/Experiments/MSSQL Defect Try-Catch Invalidates Transaction.sql new file mode 100644 index 000000000..6497e537a --- /dev/null +++ b/Experiments/MSSQL Defect Try-Catch Invalidates Transaction.sql @@ -0,0 +1,20 @@ +PRINT 'No TRY...CATCH:'; +EXEC ('BEGIN TRAN'); +SELECT XACT_STATE() AS [XACT_STATE()],@@TRANCOUNT AS [@@TRANCOUNT]; +GO +IF(XACT_STATE()<>0)ROLLBACK; +GO + + + +GO +PRINT 'In TRY...CATCH:'; +BEGIN TRY +EXEC ('BEGIN TRAN'); +END TRY +BEGIN CATCH +END CATCH; +SELECT XACT_STATE() AS [XACT_STATE()],@@TRANCOUNT AS [@@TRANCOUNT]; +GO +IF(XACT_STATE()<>0)ROLLBACK; +GO diff --git a/Experiments/SM query - read trace table.sql b/Experiments/SM query - read trace table.sql new file mode 100644 index 000000000..13d2094ca --- /dev/null +++ b/Experiments/SM query - read trace table.sql @@ -0,0 +1,61 @@ +SELECT + R.EventSequence, + TE.name, + TSV.subclass_name, + R.ObjectName, + R.LineNumber, + R.TransactionID, + R.XactSequence, + ISNULL(REPLICATE(' ',(R.NestLevel-1)),'')+CAST(R.TextData AS NVARCHAR(MAX)) AS TextData, + R.Error, + R.Severity, + R.NestLevel, + R.RowNumber, + R.EventClass, + R.EventSubClass, + R.ApplicationName, + R.ClientProcessID, + R.DatabaseID, + R.DatabaseName, + R.GroupID, + R.HostName, + R.IsSystem, + R.LoginName, + R.LoginSid, + R.NTDomainName, + R.NTUserName, + R.RequestID, + R.SPID, + R.ServerName, + R.SessionLoginName, + R.StartTime, + R.Success, + R.GUID, + R.BinaryData, + R.Duration, + R.EndTime, + R.IntegerData, + R.CPU, + R.IntegerData2, + R.Offset, + R.Reads, + R.RowCounts, + R.Writes, + R.State, + R.ObjectID, + R.ObjectType, + R.SourceDatabaseID, + R.IndexID, + R.Type, + R.Mode, + R.OwnerID, + R.ObjectID2, + R.BigintData1 + FROM dbo.run4 R + LEFT JOIN sys.trace_events AS TE + ON R.EventClass = TE.trace_event_id + LEFT JOIN sys.trace_subclass_values AS TSV + ON TSV.trace_event_id = TE.trace_event_id + AND R.EventSubClass = TSV.subclass_value + WHERE ISNULL(R.ObjectName,'???') NOT IN ('Private_Print','sp_rename', 'sp_validname','GetTestResultFormatter','sp_addextendedproperty','sp_updateextendedproperty') + ORDER BY R.EventSequence \ No newline at end of file diff --git a/Experiments/TestingTransactionNames.sql b/Experiments/TestingTransactionNames.sql new file mode 100644 index 000000000..85958a011 --- /dev/null +++ b/Experiments/TestingTransactionNames.sql @@ -0,0 +1,21 @@ +IF(XACT_STATE()<>0)ROLLBACK +GO +DROP TABLE IF EXISTS #Actual +GO +DECLARE @TranName NVARCHAR(32) = NULL; +SELECT 1,@@TRANCOUNT +SELECT 1 X,@@TRANCOUNT TC INTO #Actual +BEGIN TRAN; +SELECT 2,@@TRANCOUNT +INSERT INTO #Actual VALUES(2,@@TRANCOUNT); +SAVE TRAN @TranName; +SELECT 3,@@TRANCOUNT +SELECT * FROM sys.dm_tran_current_transaction AS DTCT JOIN sys.dm_tran_session_transactions +AS DTAT ON DTAT.transaction_id = DTCT.transaction_id +INSERT INTO #Actual VALUES(3,@@TRANCOUNT); +SELECT * FROM #Actual AS A +ROLLBACK TRAN @TranName; +SELECT 4,@@TRANCOUNT +INSERT INTO #Actual VALUES(4,@@TRANCOUNT); +COMMIT; +SELECT * FROM #Actual AS A diff --git a/Facade/Facade.CreateAllObjects.ssp.sql b/Facade/Facade.CreateAllObjects.ssp.sql index 6d0e462c6..955d4c3a7 100644 --- a/Facade/Facade.CreateAllObjects.ssp.sql +++ b/Facade/Facade.CreateAllObjects.ssp.sql @@ -45,6 +45,7 @@ BEGIN @CreateProcedureStatement = @CreateProcedureStatement OUT, @LogTableName = NULL, @CommandToExecute = NULL, + @CallOriginal = 0, @CreateLogTableStatement = NULL; EXEC Facade.CreateSchemaIfNotExists @FacadeDbName = @FacadeDbName, @SchemaName = @SchemaName; diff --git a/Source/BuildOrder.txt b/Source/BuildOrder.txt index e70ba5f24..df1c46aeb 100644 --- a/Source/BuildOrder.txt +++ b/Source/BuildOrder.txt @@ -3,6 +3,7 @@ tSQLt._Header.sql tSQLt.schema.sql tSQLt.TestClass.user.sql tSQLt.Private_GetDropItemCmd.sfn.sql +tSQLt.Private_Results.view.sql tSQLt.DropClass.ssp.sql tSQLt.Uninstall.ssp.sql tSQLt.TestClasses.view.sql @@ -41,6 +42,8 @@ tSQLt.Private_RenameObjectToUniqueName.ssp.sql tSQLt.Private_RenameObjectToUniqueNameUsingObjectId.ssp.sql tSQLt.RemoveObject.ssp.sql tSQLt.RemoveObjectIfExists.ssp.sql +tSQLt.Private_GetFormattedErrorInfo.sfn.sql +tSQLt.Private_HandleMessageAndResult.sfn.sql tSQLt.Private_CleanTestResult.ssp.sql tSQLt.Private_ListTestAnnotations.sfn.sql tSQLt.Private_ProcessTestAnnotations.ssp.sql @@ -50,8 +53,16 @@ tSQLt.Private_SqlVersion.sfn.sql tSQLt.Private_SplitSqlVersion.sfn.sql tSQLt.FriendlySQLServerVersion.sfn.sql tSQLt.Info.sfn.sql +tSQLt.Private_Seize.tbl.sql tSQLt.Private_Init.ssp.sql tSQLt.Private_HostPlatform.svw.sql +tSQLt.Private_NoTransactionTableAction.view.sql +tSQLt.Private_NoTransactionHandleTable.ssp.sql +tSQLt.Private_NoTransactionHandleTables.ssp.sql +tSQLt.Private_CleanUpCmdHandler.ssp.sql +tSQLt.Private_CleanUp.ssp.sql +tSQLt.Private_AssertNoSideEffects_GenerateCommand.sfn.sql +tSQLt.Private_AssertNoSideEffects.ssp.sql Run_Methods.sql tSQLt.Private_SysTypes.svw.sql tSQLt.Private_GetFullTypeName.sfn.sql @@ -60,6 +71,7 @@ tSQLt.Private_ScriptIndex.sfn.sql tSQLt.Private_RemoveSchemaBinding.ssp.sql tSQLt.Private_RemoveSchemaBoundReferences.ssp.sql tSQLt.Private_GetForeignKeyDefinition.sfn.sql +tSQLt.Private_MarktSQLtTempObject.ssp.sql ApplyConstraint_Methods.sql tSQLt.Private_ValidateFakeTableParameters.ssp.sql tSQLt.Private_GetDataTypeOrComputedColumnDefinition.sfn.sql @@ -68,17 +80,16 @@ tSQLt.Private_GetDefaultConstraintDefinition.sfn.sql tSQLt.Private_GetUniqueConstraintDefinition.sfn.sql tSQLt.Private_CreateFakeTableStatement.sfn.sql tSQLt.Private_CreateFakeOfTable.ssp.sql -tSQLt.Private_MarktSQLtTempObject.ssp.sql tSQLt.FakeTable.ssp.sql tSQLt.Private_GenerateCreateProcedureSpyStatement.ssp.sql tSQLt.Private_CreateProcedureSpy.ssp.sql +tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure.ssp.sql tSQLt.SpyProcedure.ssp.sql tSQLt.Private_GetCommaSeparatedColumnList.sfn.sql tSQLt.Private_CreateResultTableForCompareTables.ssp.sql tSQLt.Private_ValidateThatAllDataTypesInTableAreSupported.ssp.sql tSQLt.Private_CompareTablesFailIfUnequalRowsExists.ssp.sql tSQLt.Private_CompareTables.ssp.sql -tSQLt.Private_NullCellTable.tbl.sql tSQLt.AssertObjectExists.ssp.sql tSQLt.AssertObjectDoesNotExist.ssp.sql tSQLt.AssertEqualsString.ssp.sql @@ -94,7 +105,6 @@ tSQLt.Private_ValidateObjectsCompatibleWithFakeFunction.ssp.sql tSQLt.Private_CreateFakeFunction.ssp.sql tSQLt.FakeFunction.ssp.sql tSQLt.RenameClass.ssp.sql -tSQLt.AssertEqualsTableSchema.tables.sql tSQLt.AssertEqualsTableSchema.ssp.sql tSQLt.AssertStringTable.udt.sql tSQLt.AssertStringIn.ssp.sql @@ -105,6 +115,7 @@ tSQLt.(at)tSQLt_SkipTest.sfn.sql tSQLt.(at)tSQLt_MinSqlMajorVersion.sfn.sql tSQLt.(at)tSQLt_MaxSqlMajorVersion.sfn.sql tSQLt.(at)tSQLt_RunOnlyOnHostPlatform.sfn.sql +tSQLt.(at)tSQLt_NoTransaction.sfn.sql tSQLt.RemoveExternalAccessKey.ssp.sql tSQLt.InstallExternalAccessKey.ssp.sql tSQLt.Private_InstallationInfo.sfn.sql diff --git a/Source/Run_Methods.sql b/Source/Run_Methods.sql index b67d36bf9..2395b8896 100644 --- a/Source/Run_Methods.sql +++ b/Source/Run_Methods.sql @@ -1,5 +1,6 @@ -IF OBJECT_ID('tSQLt.Private_GetSetupProcedureName') IS NOT NULL DROP PROCEDURE tSQLt.Private_GetSetupProcedureName; +IF OBJECT_ID('tSQLt.Private_GetClassHelperProcedureName') IS NOT NULL DROP PROCEDURE tSQLt.Private_GetClassHelperProcedureName; IF OBJECT_ID('tSQLt.Private_RunTest') IS NOT NULL DROP PROCEDURE tSQLt.Private_RunTest; +IF OBJECT_ID('tSQLt.Private_RunTest_TestExecution') IS NOT NULL DROP PROCEDURE tSQLt.Private_RunTest_TestExecution; IF OBJECT_ID('tSQLt.Private_RunTestClass') IS NOT NULL DROP PROCEDURE tSQLt.Private_RunTestClass; IF OBJECT_ID('tSQLt.Private_Run') IS NOT NULL DROP PROCEDURE tSQLt.Private_Run; IF OBJECT_ID('tSQLt.Private_RunCursor') IS NOT NULL DROP PROCEDURE tSQLt.Private_RunCursor; @@ -24,116 +25,83 @@ IF OBJECT_ID('tSQLt.Private_PrepareTestResultForOutput') IS NOT NULL DROP FUNCTI GO ---Build+ -CREATE PROCEDURE tSQLt.Private_GetSetupProcedureName +CREATE PROCEDURE tSQLt.Private_GetClassHelperProcedureName @TestClassId INT = NULL, - @SetupProcName NVARCHAR(MAX) OUTPUT + @SetupProcName NVARCHAR(MAX) OUTPUT, + @CleanUpProcName NVARCHAR(MAX) OUTPUT AS BEGIN SELECT @SetupProcName = tSQLt.Private_GetQuotedFullName(object_id) FROM sys.procedures WHERE schema_id = @TestClassId AND LOWER(name) = 'setup'; + SELECT @CleanUpProcName = tSQLt.Private_GetQuotedFullName(object_id) + FROM sys.procedures + WHERE schema_id = @TestClassId + AND LOWER(name) = 'cleanup'; END; GO -CREATE PROCEDURE tSQLt.Private_RunTest - @TestName NVARCHAR(MAX), - @SetUp NVARCHAR(MAX) = NULL +CREATE PROCEDURE tSQLt.Private_RunTest_TestExecution + @TestName NVARCHAR(MAX), + @SetUp NVARCHAR(MAX), + @CleanUp NVARCHAR(MAX), + @NoTransactionFlag BIT, + @TranName CHAR(32), + @Result NVARCHAR(MAX) OUTPUT, + @Msg NVARCHAR(MAX) OUTPUT, + @TestEndTime DATETIME2 OUTPUT AS BEGIN - DECLARE @Msg NVARCHAR(MAX); SET @Msg = ''; - DECLARE @Msg2 NVARCHAR(MAX); SET @Msg2 = ''; - DECLARE @Cmd NVARCHAR(MAX); SET @Cmd = ''; - DECLARE @TestClassName NVARCHAR(MAX); SET @TestClassName = ''; - DECLARE @TestProcName NVARCHAR(MAX); SET @TestProcName = ''; - DECLARE @Result NVARCHAR(MAX); - DECLARE @TranName CHAR(32); EXEC tSQLt.GetNewTranName @TranName OUT; - DECLARE @TestResultId INT; - DECLARE @PreExecTrancount INT; - DECLARE @TestObjectId INT; - - DECLARE @VerboseMsg NVARCHAR(MAX); - DECLARE @Verbose BIT; - SET @Verbose = ISNULL((SELECT CAST(Value AS BIT) FROM tSQLt.Private_GetConfiguration('Verbose')),0); - - TRUNCATE TABLE tSQLt.CaptureOutputLog; - CREATE TABLE #ExpectException(ExpectException INT,ExpectedMessage NVARCHAR(MAX), ExpectedSeverity INT, ExpectedState INT, ExpectedMessagePattern NVARCHAR(MAX), ExpectedErrorNumber INT, FailMessage NVARCHAR(MAX)); - CREATE TABLE #SkipTest(SkipTestMessage NVARCHAR(MAX) DEFAULT ''); - - IF EXISTS (SELECT 1 FROM sys.extended_properties WHERE name = N'SetFakeViewOnTrigger') - BEGIN - RAISERROR('Test system is in an invalid state. SetFakeViewOff must be called if SetFakeViewOn was called. Call SetFakeViewOff after creating all test case procedures.', 16, 10) WITH NOWAIT; - RETURN -1; - END; - - SELECT @Cmd = 'EXEC ' + @TestName; - - SELECT @TestClassName = OBJECT_SCHEMA_NAME(OBJECT_ID(@TestName)), --tSQLt.Private_GetCleanSchemaName('', @TestName), - @TestProcName = tSQLt.Private_GetCleanObjectName(@TestName), - @TestObjectId = OBJECT_ID(@TestName); - - INSERT INTO tSQLt.TestResult(Class, TestCase, TranName, Result) - SELECT @TestClassName, @TestProcName, @TranName, 'A severe error happened during test execution. Test did not finish.' - OPTION(MAXDOP 1); - SELECT @TestResultId = SCOPE_IDENTITY(); - - IF(@Verbose = 1) - BEGIN - SET @VerboseMsg = 'tSQLt.Run '''+@TestName+'''; --Starting'; - EXEC tSQLt.Private_Print @Message =@VerboseMsg, @Severity = 0; - END; - - - SET @Result = 'Success'; - BEGIN TRAN; - SAVE TRAN @TranName; - - SET @PreExecTrancount = @@TRANCOUNT; - - - TRUNCATE TABLE tSQLt.TestMessage; + DECLARE @TransactionStartedFlag BIT = 0; + DECLARE @PreExecTrancount INT = NULL; + DECLARE @TestExecutionCmd NVARCHAR(MAX) = 'EXEC ' + @TestName; + DECLARE @CleanUpProcedureExecutionCmd NVARCHAR(MAX) = NULL; BEGIN TRY - EXEC tSQLt.Private_ProcessTestAnnotations @TestObjectId=@TestObjectId; + IF(@NoTransactionFlag = 0) + BEGIN + BEGIN TRAN; + SET @TransactionStartedFlag = 1; + SAVE TRAN @TranName; + END; + ELSE + BEGIN + SELECT object_id ObjectId, SCHEMA_NAME(schema_id) SchemaName, name ObjectName, type_desc ObjectType INTO #BeforeExecutionObjectSnapshot FROM sys.objects; + EXEC tSQLt.Private_NoTransactionHandleTables @Action = 'Save'; + END; + + SET @PreExecTrancount = @@TRANCOUNT; + DECLARE @TmpMsg NVARCHAR(MAX); - DECLARE @TestEndTime DATETIME2; SET @TestEndTime = NULL; + SET @TestEndTime = NULL; BEGIN TRY - IF(NOT EXISTS(SELECT 1 FROM #SkipTest)) + IF (@SetUp IS NOT NULL) BEGIN - IF (@SetUp IS NOT NULL) EXEC @SetUp; - EXEC (@Cmd); - IF(EXISTS(SELECT 1 FROM #ExpectException WHERE ExpectException = 1)) - BEGIN - SET @TmpMsg = COALESCE((SELECT FailMessage FROM #ExpectException)+' ','')+'Expected an error to be raised.'; - EXEC tSQLt.Fail @TmpMsg; - END + EXEC @SetUp; END; - ELSE + + EXEC (@TestExecutionCmd); + + IF(EXISTS(SELECT 1 FROM #ExpectException WHERE ExpectException = 1)) BEGIN - SELECT - @Result = 'Skipped', - @Msg = ST.SkipTestMessage - FROM #SkipTest AS ST; - SET @TmpMsg = '-->'+@TestName+' skipped: '+@Msg; - EXEC tSQLt.Private_Print @Message = @TmpMsg; - END; + SET @TmpMsg = COALESCE((SELECT FailMessage FROM #ExpectException)+' ','')+'Expected an error to be raised.'; + EXEC tSQLt.Fail @TmpMsg; + END SET @TestEndTime = SYSDATETIME(); END TRY BEGIN CATCH SET @TestEndTime = ISNULL(@TestEndTime,SYSDATETIME()); IF ERROR_MESSAGE() LIKE '%tSQLt.Failure%' BEGIN - SELECT @Msg = Msg FROM tSQLt.TestMessage; + SELECT @Msg = Msg FROM #TestMessage; SET @Result = 'Failure'; END ELSE BEGIN DECLARE @ErrorInfo NVARCHAR(MAX); - SELECT @ErrorInfo = - COALESCE(ERROR_MESSAGE(), '') + - '[' +COALESCE(LTRIM(STR(ERROR_SEVERITY())), '') + ','+COALESCE(LTRIM(STR(ERROR_STATE())), '') + ']' + - '{' + COALESCE(ERROR_PROCEDURE(), '') + ',' + COALESCE(CAST(ERROR_LINE() AS NVARCHAR), '') + '}'; + SELECT @ErrorInfo = FormattedError FROM tSQLt.Private_GetFormattedErrorInfo(); IF(EXISTS(SELECT 1 FROM #ExpectException)) BEGIN @@ -220,8 +188,14 @@ BEGIN SET @Msg = ERROR_MESSAGE(); END CATCH + --TODO:NoTran + ---- Compare @@Trancount, throw up arms if it doesn't match + --TODO:NoTran BEGIN TRY + IF(@TransactionStartedFlag = 1) + BEGIN ROLLBACK TRAN @TranName; + END; END TRY BEGIN CATCH DECLARE @PostExecTrancount INT; @@ -232,17 +206,151 @@ BEGIN OR @PostExecTrancount <> 0 ) BEGIN - SELECT @Msg = COALESCE(@Msg, '') + ' (There was also a ROLLBACK ERROR --> ' + COALESCE(ERROR_MESSAGE(), '') + '{' + COALESCE(ERROR_PROCEDURE(), '') + ',' + COALESCE(CAST(ERROR_LINE() AS NVARCHAR), '') + '})'; + SELECT @Msg = COALESCE(@Msg, '') + ' (There was also a ROLLBACK ERROR --> ' + FormattedError + ')' FROM tSQLt.Private_GetFormattedErrorInfo(); SET @Result = 'Error'; END; END CATCH; + IF (@NoTransactionFlag = 1) + BEGIN + SET @CleanUpProcedureExecutionCmd = ( + ( + SELECT 'EXEC tSQLt.Private_CleanUpCmdHandler ''EXEC '+ REPLACE(NT.CleanUpProcedureName,'''','''''') +';'', @Result OUT, @Msg OUT;' + FROM #NoTransaction NT + ORDER BY OrderId + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') + ); + IF(@CleanUpProcedureExecutionCmd IS NOT NULL) + BEGIN + EXEC sys.sp_executesql @CleanUpProcedureExecutionCmd, N'@Result NVARCHAR(MAX) OUTPUT, @Msg NVARCHAR(MAX) OUTPUT', @Result OUT, @Msg OUT; + END; + + IF(@CleanUp IS NOT NULL) + BEGIN + EXEC tSQLt.Private_CleanUpCmdHandler @CleanUp, @Result OUT, @Msg OUT; + END; + + DECLARE @CleanUpErrorMsg NVARCHAR(MAX); + EXEC tSQLt.Private_CleanUp @FullTestName = @TestName, @Result = @Result OUT, @ErrorMsg = @CleanUpErrorMsg OUT; + SET @Msg = @Msg + ISNULL(' ' + @CleanUpErrorMsg, ''); + + SELECT object_id ObjectId, SCHEMA_NAME(schema_id) SchemaName, name ObjectName, type_desc ObjectType INTO #AfterExecutionObjectSnapshot FROM sys.objects; + EXEC tSQLt.Private_AssertNoSideEffects + @BeforeExecutionObjectSnapshotTableName ='#BeforeExecutionObjectSnapshot', + @AfterExecutionObjectSnapshotTableName = '#AfterExecutionObjectSnapshot', + @TestResult = @Result OUT, + @TestMsg = @Msg OUT + END; + IF(@TransactionStartedFlag = 1) + BEGIN + COMMIT; + END; +END; +GO +CREATE PROCEDURE tSQLt.Private_RunTest + @TestName NVARCHAR(MAX), + @SetUp NVARCHAR(MAX) = NULL, + @CleanUp NVARCHAR(MAX) = NULL +AS +BEGIN + DECLARE @OuterPerimeterTrancount INT = @@TRANCOUNT; + + DECLARE @Msg NVARCHAR(MAX); SET @Msg = ''; + DECLARE @Msg2 NVARCHAR(MAX); SET @Msg2 = ''; + DECLARE @TestClassName NVARCHAR(MAX); SET @TestClassName = ''; + DECLARE @TestProcName NVARCHAR(MAX); SET @TestProcName = ''; + DECLARE @Result NVARCHAR(MAX); + DECLARE @TranName CHAR(32) = NULL; + DECLARE @TestResultId INT; + DECLARE @TestObjectId INT; + DECLARE @TestEndTime DATETIME2 = NULL; + + DECLARE @VerboseMsg NVARCHAR(MAX); + DECLARE @Verbose BIT; + SET @Verbose = ISNULL((SELECT CAST(Value AS BIT) FROM tSQLt.Private_GetConfiguration('Verbose')),0); + + TRUNCATE TABLE tSQLt.CaptureOutputLog; + CREATE TABLE #TestMessage(Msg NVARCHAR(MAX)); + CREATE TABLE #ExpectException(ExpectException INT,ExpectedMessage NVARCHAR(MAX), ExpectedSeverity INT, ExpectedState INT, ExpectedMessagePattern NVARCHAR(MAX), ExpectedErrorNumber INT, FailMessage NVARCHAR(MAX)); + CREATE TABLE #SkipTest(SkipTestMessage NVARCHAR(MAX) DEFAULT ''); + CREATE TABLE #NoTransaction(OrderId INT IDENTITY(1,1),CleanUpProcedureName NVARCHAR(MAX)); + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + + + IF EXISTS (SELECT 1 FROM sys.extended_properties WHERE name = N'SetFakeViewOnTrigger') + BEGIN + RAISERROR('Test system is in an invalid state. SetFakeViewOff must be called if SetFakeViewOn was called. Call SetFakeViewOff after creating all test case procedures.', 16, 10) WITH NOWAIT; + RETURN -1; + END; + + + SELECT @TestClassName = OBJECT_SCHEMA_NAME(OBJECT_ID(@TestName)), + @TestProcName = tSQLt.Private_GetCleanObjectName(@TestName), + @TestObjectId = OBJECT_ID(@TestName); + + INSERT INTO tSQLt.TestResult(Class, TestCase, TranName, Result) + SELECT @TestClassName, @TestProcName, @TranName, 'A severe error happened during test execution. Test did not finish.' + OPTION(MAXDOP 1); + SELECT @TestResultId = SCOPE_IDENTITY(); + + IF(@Verbose = 1) + BEGIN + SET @VerboseMsg = 'tSQLt.Run '''+@TestName+'''; --Starting'; + EXEC tSQLt.Private_Print @Message =@VerboseMsg, @Severity = 0; + END; + + + SET @Result = 'Success'; + DECLARE @SkipTestFlag BIT = 0; + DECLARE @NoTransactionFlag BIT = 0; + + BEGIN TRY + EXEC tSQLt.Private_ProcessTestAnnotations @TestObjectId=@TestObjectId; + SET @SkipTestFlag = CASE WHEN EXISTS(SELECT 1 FROM #SkipTest) THEN 1 ELSE 0 END; + SET @NoTransactionFlag = CASE WHEN EXISTS(SELECT 1 FROM #NoTransaction) THEN 1 ELSE 0 END; + + IF(@SkipTestFlag = 0) + BEGIN + IF(@NoTransactionFlag = 0) + BEGIN + EXEC tSQLt.GetNewTranName @TranName OUT; + UPDATE tSQLt.TestResult SET TranName = @TranName WHERE Id = @TestResultId; + END; + EXEC tSQLt.Private_RunTest_TestExecution + @TestName, + @SetUp, + @CleanUp, + @NoTransactionFlag, + @TranName, + @Result OUT, + @Msg OUT, + @TestEndTime OUT; + + END; + ELSE + BEGIN + DECLARE @TmpMsg NVARCHAR(MAX); + SELECT + @Result = 'Skipped', + @Msg = ST.SkipTestMessage + FROM #SkipTest AS ST; + SET @TmpMsg = '-->'+@TestName+' skipped: '+@Msg; + EXEC tSQLt.Private_Print @Message = @TmpMsg; + SET @TestEndTime = SYSDATETIME(); + END; + END TRY + BEGIN CATCH + SET @Result = 'Error'; + SET @Msg = ISNULL(NULLIF(@Msg,'') + ' ','')+ERROR_MESSAGE(); + --SET @TestEndTime = SYSDATETIME(); + END CATCH; +---------------------------------------------------------------------------------------------- If(@Result NOT IN ('Success','Skipped')) BEGIN SET @Msg2 = @TestName + ' failed: (' + @Result + ') ' + @Msg; EXEC tSQLt.Private_Print @Message = @Msg2, @Severity = 0; END; - IF EXISTS(SELECT 1 FROM tSQLt.TestResult WHERE Id = @TestResultId) BEGIN UPDATE tSQLt.TestResult SET @@ -260,14 +368,27 @@ BEGIN 'Error', 'TestResult entry is missing; Original outcome: ' + @Result + ', ' + @Msg; END; - COMMIT; IF(@Verbose = 1) BEGIN - SET @VerboseMsg = 'tSQLt.Run '''+@TestName+'''; --Finished'; + SET @VerboseMsg = 'tSQLt.Run '''+@TestName+'''; --Finished'; EXEC tSQLt.Private_Print @Message =@VerboseMsg, @Severity = 0; + --DECLARE @AsciiArtLine NVARCHAR(MAX) = CASE WHEN @Result<>'Success' THEN REPLICATE(CHAR(168),150)+' '+CHAR(155)+CHAR(155)+' '+@Result + ' ' +CHAR(139)+CHAR(139) ELSE '' END + CHAR(13)+CHAR(10) + CHAR(173); + --EXEC tSQLt.Private_Print @Message = @AsciiArtLine, @Severity = 0; + END; + + IF(@Result = 'FATAL') + BEGIN + INSERT INTO tSQLt.Private_Seize VALUES(1); + RAISERROR('The last test has invalidated the current installation of tSQLt. Please reinstall tSQLt.',16,10); + END; + IF(@Result = 'Abort') + BEGIN + RAISERROR('Aborting the current execution of tSQLt due to a severe error.', 16, 10); END; + IF(@OuterPerimeterTrancount != @@TRANCOUNT) RAISERROR('tSQLt is in an invalid state: Stopping Execution. (Mismatching TRANCOUNT: %i <> %i))',16,10,@OuterPerimeterTrancount, @@TRANCOUNT); + END; GO @@ -278,28 +399,20 @@ BEGIN DECLARE @TestCaseName NVARCHAR(MAX); DECLARE @TestClassId INT; SET @TestClassId = tSQLt.Private_GetSchemaId(@TestClassName); DECLARE @SetupProcName NVARCHAR(MAX); - EXEC tSQLt.Private_GetSetupProcedureName @TestClassId, @SetupProcName OUTPUT; - - DECLARE testCases CURSOR LOCAL FAST_FORWARD - FOR - SELECT tSQLt.Private_GetQuotedFullName(object_id) - FROM sys.procedures - WHERE schema_id = @TestClassId - AND LOWER(name) LIKE 'test%'; - - OPEN testCases; + DECLARE @CleanUpProcName NVARCHAR(MAX); + EXEC tSQLt.Private_GetClassHelperProcedureName @TestClassId, @SetupProcName OUT, @CleanUpProcName OUT; - FETCH NEXT FROM testCases INTO @TestCaseName; - - WHILE @@FETCH_STATUS = 0 - BEGIN - EXEC tSQLt.Private_RunTest @TestCaseName, @SetupProcName; - - FETCH NEXT FROM testCases INTO @TestCaseName; - END; - - CLOSE testCases; - DEALLOCATE testCases; + DECLARE @cmd NVARCHAR(MAX) = ( + ( + SELECT 'EXEC tSQLt.Private_RunTest '''+REPLACE(tSQLt.Private_GetQuotedFullName(object_id),'''','''''')+''', '+ISNULL(''''+REPLACE(@SetupProcName,'''','''''')+'''','NULL')+', '+ISNULL(''''+REPLACE(@CleanUpProcName,'''','''''')+'''','NULL')+';' + FROM sys.procedures + WHERE schema_id = @TestClassId + AND LOWER(name) LIKE 'test%' + ORDER BY NEWID() + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') + ); + EXEC(@cmd); END; GO @@ -316,7 +429,7 @@ SET NOCOUNT ON; DECLARE @IsSchema BIT; DECLARE @SetUp NVARCHAR(MAX);SET @SetUp = NULL; - SELECT @TestName = tSQLt.Private_GetLastTestNameIfNotProvided(@TestName); + SELECT @TestName = TestName FROM tSQLt.Private_GetLastTestNameIfNotProvided(@TestName); EXEC tSQLt.Private_SaveTestNameForSession @TestName; SELECT @TestClassId = schemaId, @@ -334,9 +447,10 @@ SET NOCOUNT ON; IF @IsTestCase = 1 BEGIN DECLARE @SetupProcName NVARCHAR(MAX); - EXEC tSQLt.Private_GetSetupProcedureName @TestClassId, @SetupProcName OUTPUT; + DECLARE @CleanUpProcName NVARCHAR(MAX); + EXEC tSQLt.Private_GetClassHelperProcedureName @TestClassId, @SetupProcName OUT, @CleanUpProcName OUT; - EXEC tSQLt.Private_RunTest @FullName, @SetupProcName; + EXEC tSQLt.Private_RunTest @FullName, @SetupProcName, @CleanUpProcName; END; EXEC tSQLt.Private_OutputTestResults @TestResultFormatter; @@ -353,34 +467,28 @@ BEGIN DECLARE @TestClassName NVARCHAR(MAX); DECLARE @TestProcName NVARCHAR(MAX); - DECLARE @TestClassCursor CURSOR; - EXEC @GetCursorCallback @TestClassCursor = @TestClassCursor OUT; + CREATE TABLE #TestClassesForRunCursor(Name NVARCHAR(MAX)); + EXEC @GetCursorCallback; ---- - WHILE(1=1) - BEGIN - FETCH NEXT FROM @TestClassCursor INTO @TestClassName; - IF(@@FETCH_STATUS<>0)BREAK; - - EXEC tSQLt.Private_RunTestClass @TestClassName; - - END; - - CLOSE @TestClassCursor; - DEALLOCATE @TestClassCursor; + DECLARE @cmd NVARCHAR(MAX) = ( + ( + SELECT 'EXEC tSQLt.Private_RunTestClass '''+REPLACE(Name, '''' ,'''''')+''';' + FROM #TestClassesForRunCursor + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') + ); + EXEC(@cmd); EXEC tSQLt.Private_OutputTestResults @TestResultFormatter; END; GO CREATE PROCEDURE tSQLt.Private_GetCursorForRunAll - @TestClassCursor CURSOR VARYING OUTPUT AS BEGIN - SET @TestClassCursor = CURSOR LOCAL FAST_FORWARD FOR + INSERT INTO #TestClassesForRunCursor SELECT Name FROM tSQLt.TestClasses; - - OPEN @TestClassCursor; END; GO @@ -393,16 +501,13 @@ END; GO CREATE PROCEDURE tSQLt.Private_GetCursorForRunNew - @TestClassCursor CURSOR VARYING OUTPUT AS BEGIN - SET @TestClassCursor = CURSOR LOCAL FAST_FORWARD FOR + INSERT INTO #TestClassesForRunCursor SELECT TC.Name FROM tSQLt.TestClasses AS TC JOIN tSQLt.Private_NewTestClassList AS PNTCL ON PNTCL.ClassName = TC.Name; - - OPEN @TestClassCursor; END; GO @@ -846,3 +951,14 @@ BEGIN END GO --Build- + + + + --SELECT 3 X, @SkipTestFlag SkipTestFlag, + -- @NoTransactionFlag NoTransactionFlag, + -- @TransactionStartedFlag TransactionStartedFlag, + -- @PreExecTrancount PreExecTrancount, + -- @@TRANCOUNT Trancount, + -- @TestName TestName, + -- @Result Result, + -- @Msg Msg; diff --git a/Source/Source.ssmssqlproj b/Source/Source.ssmssqlproj index 9bf6e3537..85f3316e9 100644 --- a/Source/Source.ssmssqlproj +++ b/Source/Source.ssmssqlproj @@ -2,6 +2,8 @@ + + @@ -18,9 +20,6 @@ ExecutePrepareServer.sql - - - Run_Methods.sql @@ -35,6 +34,12 @@ tSQLt.(at)tSQLt_MinSqlMajorVersion.sfn.sql + + + + + tSQLt.(at)tSQLt_NoTransaction.sfn.sql + @@ -77,12 +82,6 @@ tSQLt.AssertEqualsTableSchema.ssp.sql - - - - - tSQLt.AssertEqualsTableSchema.tables.sql - @@ -209,12 +208,27 @@ tSQLt.PrepareServer.ssp.sql + + tSQLt.Private_AssertNoSideEffects.ssp.sql + + + tSQLt.Private_AssertNoSideEffects_GenerateCommand.sfn.sql + tSQLt.Private_CleanTestResult.ssp.sql + + + + + tSQLt.Private_CleanUp.ssp.sql + + + tSQLt.Private_CleanUpCmdHandler.ssp.sql + @@ -329,6 +343,12 @@ tSQLt.Private_GetForeignKeyDefinition.sfn.sql + + + + + tSQLt.Private_GetFormattedErrorInfo.sfn.sql + @@ -353,6 +373,12 @@ tSQLt.Private_GetUniqueConstraintDefinition.sfn.sql + + + + + tSQLt.Private_HandleMessageAndResult.sfn.sql + @@ -366,9 +392,6 @@ tSQLt.Private_HostPlatform.svw.sql - - - tSQLt.Private_Init.ssp.sql @@ -389,6 +412,12 @@ tSQLt.Private_ListTestAnnotations.sfn.sql + + + + + tSQLt.Private_Lock.tbl.sql + @@ -413,12 +442,6 @@ tSQLt.Private_NewTestClassList.tbl.sql - - - - - tSQLt.Private_NullCellTable.tbl.sql - @@ -479,12 +502,42 @@ tSQLt.Private_ResetNewTestClassList.ssp.sql + + + + + tSQLt.Private_NoTransactionHandleTable.ssp.sql + + + + + + tSQLt.Private_NoTransactionTableAction.view.sql + + + + + + tSQLt.Private_NoTransactionHandleTables.ssp.sql + + + + + + tSQLt.Private_Results.view.sql + tSQLt.Private_ScriptIndex.sfn.sql + + + + + tSQLt.Private_Seize.tbl.sql + @@ -539,6 +592,12 @@ tSQLt.Private_ValidateObjectsCompatibleWithFakeFunction.ssp.sql + + + + + tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure.ssp.sql + diff --git a/Source/tSQLt.(at)tSQLt_MinSqlMajorVersion.sfn.sql b/Source/tSQLt.(at)tSQLt_MinSqlMajorVersion.sfn.sql index 15bd9bdef..0f970159b 100644 --- a/Source/tSQLt.(at)tSQLt_MinSqlMajorVersion.sfn.sql +++ b/Source/tSQLt.(at)tSQLt_MinSqlMajorVersion.sfn.sql @@ -7,7 +7,12 @@ RETURNS TABLE AS RETURN SELECT AF.* - FROM (SELECT PSSV.Major FROM tSQLt.Private_SqlVersion() AS PSV CROSS APPLY tSQLt.Private_SplitSqlVersion(PSV.ProductVersion) AS PSSV) AV + FROM + ( + SELECT PSSV.Major + FROM tSQLt.Private_SqlVersion() AS PSV + CROSS APPLY tSQLt.Private_SplitSqlVersion(PSV.ProductVersion) AS PSSV + ) AV CROSS APPLY tSQLt.[@tSQLt:SkipTest]('Minimum required version is '+ CAST(@MinVersion AS NVARCHAR(MAX))+ ', but current version is '+ diff --git a/Source/tSQLt.(at)tSQLt_NoTransaction.sfn.sql b/Source/tSQLt.(at)tSQLt_NoTransaction.sfn.sql new file mode 100644 index 000000000..60bb2c6af --- /dev/null +++ b/Source/tSQLt.(at)tSQLt_NoTransaction.sfn.sql @@ -0,0 +1,18 @@ +IF OBJECT_ID('tSQLt.[@tSQLt:NoTransaction]') IS NOT NULL DROP FUNCTION tSQLt.[@tSQLt:NoTransaction]; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.[@tSQLt:NoTransaction](@CleanUpProcedureName NVARCHAR(MAX) = NULL) +RETURNS TABLE +AS +RETURN + SELECT + CASE + WHEN (X.QuotedName IS NULL) + THEN 'INSERT INTO #NoTransaction VALUES(NULL);' + ELSE 'IF(NOT EXISTS (SELECT 1 FROM sys.procedures WHERE object_id = OBJECT_ID('+X.QuotedName+'))) BEGIN RAISERROR(''Test CleanUp Procedure %s does not exist or is not a procedure.'',16,10,'+X.QuotedName+'); END;INSERT INTO #NoTransaction VALUES('+X.QuotedName+');' + END AS AnnotationCmd + FROM (VALUES(''''+REPLACE(@CleanUpProcedureName,'''','''''')+''''))X(QuotedName); +GO +---Build- +GO diff --git a/Source/tSQLt.AssertEqualsTable.ssp.sql b/Source/tSQLt.AssertEqualsTable.ssp.sql index 555f05222..64439fd10 100644 --- a/Source/tSQLt.AssertEqualsTable.ssp.sql +++ b/Source/tSQLt.AssertEqualsTable.ssp.sql @@ -13,6 +13,7 @@ BEGIN EXEC tSQLt.AssertObjectExists @Actual; DECLARE @ResultTable NVARCHAR(MAX); + DECLARE @ResultTableWithSchema NVARCHAR(MAX); DECLARE @ResultColumn NVARCHAR(MAX); DECLARE @ColumnList NVARCHAR(MAX); DECLARE @UnequalRowsExist INT; @@ -20,27 +21,28 @@ BEGIN SELECT @ResultTable = tSQLt.Private::CreateUniqueObjectName(); SELECT @ResultColumn = 'RC_' + @ResultTable; + SELECT @ResultTableWithSchema = 'tSQLt.' + @ResultTable; EXEC tSQLt.Private_CreateResultTableForCompareTables - @ResultTable = @ResultTable, + @ResultTable = @ResultTableWithSchema, @ResultColumn = @ResultColumn, @BaseTable = @Expected; - SELECT @ColumnList = tSQLt.Private_GetCommaSeparatedColumnList(@ResultTable, @ResultColumn); + SELECT @ColumnList = tSQLt.Private_GetCommaSeparatedColumnList(@ResultTableWithSchema, @ResultColumn); - EXEC tSQLt.Private_ValidateThatAllDataTypesInTableAreSupported @ResultTable, @ColumnList; + EXEC tSQLt.Private_ValidateThatAllDataTypesInTableAreSupported @ResultTableWithSchema, @ColumnList; EXEC @UnequalRowsExist = tSQLt.Private_CompareTables @Expected = @Expected, @Actual = @Actual, - @ResultTable = @ResultTable, + @ResultTable = @ResultTableWithSchema, @ColumnList = @ColumnList, @MatchIndicatorColumnName = @ResultColumn; SET @CombinedMessage = ISNULL(@Message + CHAR(13) + CHAR(10),'') + @FailMsg; EXEC tSQLt.Private_CompareTablesFailIfUnequalRowsExists @UnequalRowsExist = @UnequalRowsExist, - @ResultTable = @ResultTable, + @ResultTable = @ResultTableWithSchema, @ResultColumn = @ResultColumn, @ColumnList = @ColumnList, @FailMsg = @CombinedMessage; diff --git a/Source/tSQLt.AssertEqualsTableSchema.ssp.sql b/Source/tSQLt.AssertEqualsTableSchema.ssp.sql index bf1807dcb..b41c60c62 100644 --- a/Source/tSQLt.AssertEqualsTableSchema.ssp.sql +++ b/Source/tSQLt.AssertEqualsTableSchema.ssp.sql @@ -8,9 +8,8 @@ CREATE PROCEDURE tSQLt.AssertEqualsTableSchema @Message NVARCHAR(MAX) = NULL AS BEGIN - INSERT INTO tSQLt.Private_AssertEqualsTableSchema_Expected([RANK(column_id)],name,system_type_id,user_type_id,max_length,precision,scale,collation_name,is_nullable) SELECT - RANK()OVER(ORDER BY C.column_id), + RANK()OVER(ORDER BY C.column_id) [RANK(column_id)], C.name, CAST(C.system_type_id AS NVARCHAR(MAX))+QUOTENAME(TS.name) system_type_id, CAST(C.user_type_id AS NVARCHAR(MAX))+CASE WHEN TU.system_type_id<> TU.user_type_id THEN QUOTENAME(SCHEMA_NAME(TU.schema_id))+'.' ELSE '' END + QUOTENAME(TU.name) user_type_id, @@ -19,15 +18,15 @@ BEGIN C.scale, C.collation_name, C.is_nullable + INTO #Expected FROM sys.columns AS C JOIN sys.types AS TS ON C.system_type_id = TS.user_type_id JOIN sys.types AS TU ON C.user_type_id = TU.user_type_id WHERE C.object_id = OBJECT_ID(@Expected); - INSERT INTO tSQLt.Private_AssertEqualsTableSchema_Actual([RANK(column_id)],name,system_type_id,user_type_id,max_length,precision,scale,collation_name,is_nullable) SELECT - RANK()OVER(ORDER BY C.column_id), + RANK()OVER(ORDER BY C.column_id) [RANK(column_id)], C.name, CAST(C.system_type_id AS NVARCHAR(MAX))+QUOTENAME(TS.name) system_type_id, CAST(C.user_type_id AS NVARCHAR(MAX))+CASE WHEN TU.system_type_id<> TU.user_type_id THEN QUOTENAME(SCHEMA_NAME(TU.schema_id))+'.' ELSE '' END + QUOTENAME(TU.name) user_type_id, @@ -36,6 +35,7 @@ BEGIN C.scale, C.collation_name, C.is_nullable + INTO #Actual FROM sys.columns AS C JOIN sys.types AS TS ON C.system_type_id = TS.user_type_id @@ -43,6 +43,6 @@ BEGIN ON C.user_type_id = TU.user_type_id WHERE C.object_id = OBJECT_ID(@Actual); - EXEC tSQLt.AssertEqualsTable 'tSQLt.Private_AssertEqualsTableSchema_Expected','tSQLt.Private_AssertEqualsTableSchema_Actual',@Message=@Message,@FailMsg='Unexpected/missing column(s)'; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual',@Message=@Message,@FailMsg='Unexpected/missing column(s)'; END; GO \ No newline at end of file diff --git a/Source/tSQLt.AssertEqualsTableSchema.tables.sql b/Source/tSQLt.AssertEqualsTableSchema.tables.sql deleted file mode 100644 index 58962af25..000000000 --- a/Source/tSQLt.AssertEqualsTableSchema.tables.sql +++ /dev/null @@ -1,28 +0,0 @@ -IF OBJECT_ID('tSQLt.Private_AssertEqualsTableSchema_Expected') IS NOT NULL DROP TABLE tSQLt.Private_AssertEqualsTableSchema_Expected; -IF OBJECT_ID('tSQLt.Private_AssertEqualsTableSchema_Actual') IS NOT NULL DROP TABLE tSQLt.Private_AssertEqualsTableSchema_Actual; -GO ----Build+ -GO -CREATE TABLE [tSQLt].[Private_AssertEqualsTableSchema_Actual] -( - name NVARCHAR(256) NULL, - [RANK(column_id)] INT NULL, - system_type_id NVARCHAR(MAX) NULL, - user_type_id NVARCHAR(MAX) NULL, - max_length SMALLINT NULL, - precision TINYINT NULL, - scale TINYINT NULL, - collation_name NVARCHAR(256) NULL, - is_nullable BIT NULL, - is_identity BIT NULL -); -GO -EXEC(' - SET NOCOUNT ON; - SELECT TOP(0) * - INTO tSQLt.Private_AssertEqualsTableSchema_Expected - FROM tSQLt.Private_AssertEqualsTableSchema_Actual AS AETSA; -'); -GO ----Build- -GO \ No newline at end of file diff --git a/Source/tSQLt.DropClass.ssp.sql b/Source/tSQLt.DropClass.ssp.sql index 5c3af0989..86c8ce058 100644 --- a/Source/tSQLt.DropClass.ssp.sql +++ b/Source/tSQLt.DropClass.ssp.sql @@ -8,13 +8,32 @@ BEGIN /*SnipStart: CreateDropClassStatement.ps1*/ DECLARE @Cmd NVARCHAR(MAX); - WITH ObjectInfo(FullName, ItemType) AS + WITH SchemaInfo(FullName, ItemType, SchemaId) AS + ( + SELECT + QUOTENAME(S.name), + 'schema', + S.schema_id + FROM sys.schemas AS S + WHERE S.schema_id = ISNULL(SCHEMA_ID(@ClassName), SCHEMA_ID(PARSENAME(@ClassName,1))) + ), + ConstraintInfo(FullName, ItemType) AS + (/*FOREIGN KEYS need to be dropped before their tables*/ + SELECT + QUOTENAME(SCHEMA_NAME(O.schema_id))+'.'+QUOTENAME(O.name), + O.type + FROM sys.objects AS O + JOIN SchemaInfo SI ON SI.SchemaId = O.schema_id + AND O.type IN ('F') + ), + ObjectInfo(FullName, ItemType) AS ( SELECT QUOTENAME(SCHEMA_NAME(O.schema_id))+'.'+QUOTENAME(O.name), O.type FROM sys.objects AS O - WHERE O.schema_id = SCHEMA_ID(@ClassName) + JOIN SchemaInfo SI ON SI.SchemaId = O.schema_id + AND O.type NOT IN ('F') ), TypeInfo(FullName, ItemType) AS ( @@ -22,7 +41,7 @@ BEGIN QUOTENAME(SCHEMA_NAME(T.schema_id))+'.'+QUOTENAME(T.name), 'type' FROM sys.types AS T - WHERE T.schema_id = SCHEMA_ID(@ClassName) + JOIN SchemaInfo SI ON SI.SchemaId = T.schema_id ), XMLSchemaInfo(FullName, ItemType) AS ( @@ -30,25 +49,20 @@ BEGIN QUOTENAME(SCHEMA_NAME(XSC.schema_id))+'.'+QUOTENAME(XSC.name), 'xml_schema_collection' FROM sys.xml_schema_collections AS XSC - WHERE XSC.schema_id = SCHEMA_ID(@ClassName) - ), - SchemaInfo(FullName, ItemType) AS - ( - SELECT - QUOTENAME(S.name), - 'schema' - FROM sys.schemas AS S - WHERE S.schema_id = SCHEMA_ID(PARSENAME(@ClassName,1)) + JOIN SchemaInfo SI ON SI.SchemaId = XSC.schema_id ), DropStatements(no,FullName,ItemType) AS ( SELECT 10, FullName, ItemType - FROM ObjectInfo + FROM ConstraintInfo UNION ALL SELECT 20, FullName, ItemType - FROM TypeInfo + FROM ObjectInfo UNION ALL SELECT 30, FullName, ItemType + FROM TypeInfo + UNION ALL + SELECT 40, FullName, ItemType FROM XMLSchemaInfo UNION ALL SELECT 10000, FullName, ItemType diff --git a/Source/tSQLt.Fail.ssp.sql b/Source/tSQLt.Fail.ssp.sql index 6153c1e42..8eb493704 100644 --- a/Source/tSQLt.Fail.ssp.sql +++ b/Source/tSQLt.Fail.ssp.sql @@ -37,7 +37,7 @@ BEGIN SAVE TRAN @TranName; END; - INSERT INTO tSQLt.TestMessage(Msg) + INSERT INTO #TestMessage(Msg) SELECT COALESCE(@Message0, '!NULL!') + COALESCE(@Message1, '!NULL!') + COALESCE(@Message2, '!NULL!') diff --git a/Source/tSQLt.FakeTable.ssp.sql b/Source/tSQLt.FakeTable.ssp.sql index 9f7e3469b..95790ea13 100644 --- a/Source/tSQLt.FakeTable.ssp.sql +++ b/Source/tSQLt.FakeTable.ssp.sql @@ -14,6 +14,8 @@ BEGIN DECLARE @OrigObjectNewName NVARCHAR(4000); DECLARE @OrigObjectFullName NVARCHAR(MAX) = NULL; DECLARE @TargetObjectFullName NVARCHAR(MAX) = NULL; + DECLARE @OriginalObjectObjectId INT; + DECLARE @TargetObjectObjectId INT; IF(@TableName NOT IN (PARSENAME(@TableName,1),QUOTENAME(PARSENAME(@TableName,1))) AND @SchemaName IS NOT NULL) @@ -31,9 +33,11 @@ BEGIN EXEC tSQLt.Private_RenameObjectToUniqueName @OrigObjectCleanQuotedSchemaName, @OrigObjectCleanQuotedName, @OrigObjectNewName OUTPUT; + SET @OriginalObjectObjectId = OBJECT_ID(@OrigObjectCleanQuotedSchemaName + '.' + QUOTENAME(@OrigObjectNewName)); + SELECT @TargetObjectFullName = S.base_object_name FROM sys.synonyms AS S - WHERE S.object_id = OBJECT_ID(@OrigObjectCleanQuotedSchemaName + '.' + @OrigObjectNewName); + WHERE S.object_id = @OriginalObjectObjectId; IF(@TargetObjectFullName IS NOT NULL) BEGIN @@ -41,13 +45,14 @@ BEGIN BEGIN RAISERROR('Cannot fake synonym %s as it is pointing to %s, which is not a table or view!',16,10,@OrigObjectFullName,@TargetObjectFullName); END; + SET @TargetObjectObjectId = OBJECT_ID(@TargetObjectFullName); END; ELSE BEGIN - SET @TargetObjectFullName = @OrigObjectCleanQuotedSchemaName + '.' + QUOTENAME(@OrigObjectNewName); --TODO:Test for QUOTENAME + SET @TargetObjectObjectId = @OriginalObjectObjectId; END; - EXEC tSQLt.Private_CreateFakeOfTable @OrigObjectCleanQuotedSchemaName, @OrigObjectCleanQuotedName, @TargetObjectFullName, @Identity, @ComputedColumns, @Defaults; + EXEC tSQLt.Private_CreateFakeOfTable @OrigObjectCleanQuotedSchemaName, @OrigObjectCleanQuotedName, @TargetObjectObjectId, @Identity, @ComputedColumns, @Defaults; EXEC tSQLt.Private_MarktSQLtTempObject @OrigObjectFullName, N'TABLE', @OrigObjectNewName; END diff --git a/Source/tSQLt.Private_AssertNoSideEffects.ssp.sql b/Source/tSQLt.Private_AssertNoSideEffects.ssp.sql new file mode 100644 index 000000000..8febaac61 --- /dev/null +++ b/Source/tSQLt.Private_AssertNoSideEffects.ssp.sql @@ -0,0 +1,15 @@ +IF OBJECT_ID('tSQLt.Private_AssertNoSideEffects') IS NOT NULL DROP PROCEDURE tSQLt.Private_AssertNoSideEffects; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_AssertNoSideEffects + @BeforeExecutionObjectSnapshotTableName NVARCHAR(MAX), + @AfterExecutionObjectSnapshotTableName NVARCHAR(MAX), + @TestResult NVARCHAR(MAX) OUTPUT, + @TestMsg NVARCHAR(MAX) OUTPUT +AS +BEGIN + DECLARE @Command NVARCHAR(MAX) = (SELECT Command FROM tSQLt.Private_AssertNoSideEffects_GenerateCommand(@BeforeExecutionObjectSnapshotTableName, @AfterExecutionObjectSnapshotTableName)); + EXEC tSQLt.Private_CleanUpCmdHandler @CleanUpCmd=@Command, @TestResult=@TestResult OUT, @TestMsg=@TestMsg OUT; +END; +GO diff --git a/Source/tSQLt.Private_AssertNoSideEffects_GenerateCommand.sfn.sql b/Source/tSQLt.Private_AssertNoSideEffects_GenerateCommand.sfn.sql new file mode 100644 index 000000000..aa179eeb3 --- /dev/null +++ b/Source/tSQLt.Private_AssertNoSideEffects_GenerateCommand.sfn.sql @@ -0,0 +1,25 @@ +IF OBJECT_ID('tSQLt.Private_AssertNoSideEffects_GenerateCommand') IS NOT NULL DROP FUNCTION tSQLt.Private_AssertNoSideEffects_GenerateCommand; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_AssertNoSideEffects_GenerateCommand( + @BeforeExecutionObjectSnapshotTableName NVARCHAR(MAX), + @AfterExecutionObjectSnapshotTableName NVARCHAR(MAX) +) +RETURNS TABLE +AS +RETURN + SELECT ' + SELECT * INTO #ObjectDiscrepancies + FROM( + (SELECT ''Deleted'' [Status], B.* FROM '+@BeforeExecutionObjectSnapshotTableName+' AS B EXCEPT SELECT ''Deleted'' [Status],* FROM '+@AfterExecutionObjectSnapshotTableName+' AS A) + UNION ALL + (SELECT ''Added'' [Status], A.* FROM '+@AfterExecutionObjectSnapshotTableName+' AS A EXCEPT SELECT ''Added'' [Status], * FROM '+@BeforeExecutionObjectSnapshotTableName+' AS B) + )D; + IF(EXISTS(SELECT 1 FROM #ObjectDiscrepancies)) + BEGIN + DECLARE @TableToText NVARCHAR(MAX); + EXEC tSQLt.TableToText @TableName = ''#ObjectDiscrepancies'' ,@txt = @TableToText OUTPUT, @OrderBy = ''[Status] ASC, SchemaName ASC, ObjectName ASC''; + RAISERROR(''After the test executed, there were unexpected or missing objects in the database: %s'',16,10,@TableToText); + END;' Command; +GO diff --git a/Source/tSQLt.Private_CleanUp.ssp.sql b/Source/tSQLt.Private_CleanUp.ssp.sql new file mode 100644 index 000000000..c262958d5 --- /dev/null +++ b/Source/tSQLt.Private_CleanUp.ssp.sql @@ -0,0 +1,27 @@ +IF OBJECT_ID('tSQLt.Private_CleanUp') IS NOT NULL DROP PROCEDURE tSQLt.Private_CleanUp; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_CleanUp + @FullTestName NVARCHAR(MAX), + @Result NVARCHAR(MAX) OUTPUT, + @ErrorMsg NVARCHAR(MAX) OUTPUT +AS +BEGIN + + EXEC tSQLt.Private_CleanUpCmdHandler + @CleanUpCmd = 'EXEC tSQLt.Private_NoTransactionHandleTables @Action=''Reset'';', + @TestResult = @Result OUT, + @TestMsg = @ErrorMsg OUT, + @ResultInCaseOfError = 'FATAL'; + + EXEC tSQLt.Private_CleanUpCmdHandler + @CleanUpCmd = 'EXEC tSQLt.UndoTestDoubles @Force = 0;', + @TestResult = @Result OUT, + @TestMsg = @ErrorMsg OUT, + @ResultInCaseOfError = 'Abort'; + +END; +GO +---Build- +GO diff --git a/Source/tSQLt.Private_CleanUpCmdHandler.ssp.sql b/Source/tSQLt.Private_CleanUpCmdHandler.ssp.sql new file mode 100644 index 000000000..e8fdd04c0 --- /dev/null +++ b/Source/tSQLt.Private_CleanUpCmdHandler.ssp.sql @@ -0,0 +1,19 @@ +IF OBJECT_ID('tSQLt.Private_CleanUpCmdHandler') IS NOT NULL DROP PROCEDURE tSQLt.Private_CleanUpCmdHandler; +GO +---Build+ +CREATE PROCEDURE tSQLt.Private_CleanUpCmdHandler + @CleanUpCmd NVARCHAR(MAX), + @TestResult NVARCHAR(MAX) OUTPUT, + @TestMsg NVARCHAR(MAX) OUTPUT, + @ResultInCaseOfError NVARCHAR(MAX) = 'Error' +AS +BEGIN + BEGIN TRY + EXEC(@CleanUpCmd); + END TRY + BEGIN CATCH + DECLARE @NewMsg NVARCHAR(MAX) = 'Error during clean up: (' + (SELECT FormattedError FROM tSQLt.Private_GetFormattedErrorInfo()) + ')'; + SELECT @TestMsg = Message, @TestResult = Result FROM tSQLt.Private_HandleMessageAndResult(@TestMsg /*PrevMsg*/, @TestResult /*PrevResult*/, @NewMsg /*NewMsg*/, @ResultInCaseOfError /*NewResult*/); + END CATCH; +END; +GO diff --git a/Source/tSQLt.Private_CreateFakeOfTable.ssp.sql b/Source/tSQLt.Private_CreateFakeOfTable.ssp.sql index 55dbbd504..625d910b8 100644 --- a/Source/tSQLt.Private_CreateFakeOfTable.ssp.sql +++ b/Source/tSQLt.Private_CreateFakeOfTable.ssp.sql @@ -4,7 +4,7 @@ GO CREATE PROCEDURE tSQLt.Private_CreateFakeOfTable @SchemaName NVARCHAR(MAX), @TableName NVARCHAR(MAX), - @OrigTableFullName NVARCHAR(MAX), + @OrigTableObjectId INT, @Identity BIT, @ComputedColumns BIT, @Defaults BIT @@ -12,7 +12,7 @@ AS BEGIN DECLARE @cmd NVARCHAR(MAX) = (SELECT CreateTableStatement - FROM tSQLt.Private_CreateFakeTableStatement(OBJECT_ID(@OrigTableFullName), @SchemaName+'.'+@TableName,@Identity,@ComputedColumns,@Defaults,0)); + FROM tSQLt.Private_CreateFakeTableStatement(@OrigTableObjectId, @SchemaName+'.'+@TableName,@Identity,@ComputedColumns,@Defaults,0)); EXEC (@cmd); END; ---Build- diff --git a/Source/tSQLt.Private_CreateResultTableForCompareTables.ssp.sql b/Source/tSQLt.Private_CreateResultTableForCompareTables.ssp.sql index 17055733f..5f629bab2 100644 --- a/Source/tSQLt.Private_CreateResultTableForCompareTables.ssp.sql +++ b/Source/tSQLt.Private_CreateResultTableForCompareTables.ssp.sql @@ -10,11 +10,10 @@ AS BEGIN DECLARE @Cmd NVARCHAR(MAX); SET @Cmd = ' - SELECT ''='' AS ' + @ResultColumn + ', Expected.* INTO ' + @ResultTable + ' - FROM tSQLt.Private_NullCellTable N - LEFT JOIN ' + @BaseTable + ' AS Expected ON N.I <> N.I - TRUNCATE TABLE ' + @ResultTable + ';' --Need to insert an actual row to prevent IDENTITY property from transfering (IDENTITY_COL can't be NULLable); + SELECT TOP(0) ''>'' AS ' + @ResultColumn + ', Expected.* INTO ' + @ResultTable + ' + FROM ' + @BaseTable + ' AS Expected RIGHT JOIN ' + @BaseTable + ' AS X ON 1=0; ' EXEC(@Cmd); + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = @ResultTable, @ObjectType = N'TABLE'; END GO ---Build- diff --git a/Source/tSQLt.Private_GenerateCreateProcedureSpyStatement.ssp.sql b/Source/tSQLt.Private_GenerateCreateProcedureSpyStatement.ssp.sql index b0b17d4bc..6176baf30 100644 --- a/Source/tSQLt.Private_GenerateCreateProcedureSpyStatement.ssp.sql +++ b/Source/tSQLt.Private_GenerateCreateProcedureSpyStatement.ssp.sql @@ -4,28 +4,28 @@ GO CREATE PROCEDURE tSQLt.Private_GenerateCreateProcedureSpyStatement @ProcedureObjectId INT, @OriginalProcedureName NVARCHAR(MAX), + @UnquotedNewNameOfProcedure NVARCHAR(MAX) = NULL, @LogTableName NVARCHAR(MAX), @CommandToExecute NVARCHAR(MAX), + @CallOriginal BIT, @CreateProcedureStatement NVARCHAR(MAX) OUTPUT, @CreateLogTableStatement NVARCHAR(MAX) OUTPUT AS BEGIN - DECLARE @ProcParmList NVARCHAR(MAX), - @TableColList NVARCHAR(MAX), - @ProcParmTypeList NVARCHAR(MAX), - @TableColTypeList NVARCHAR(MAX); + DECLARE @ProcParmListForInsert NVARCHAR(MAX) = ''; + DECLARE @ProcParmListForCall NVARCHAR(MAX) = ''; + DECLARE @TableColList NVARCHAR(MAX) = ''; + DECLARE @ProcParmTypeList NVARCHAR(MAX) = ''; + DECLARE @TableColTypeList NVARCHAR(MAX) = ''; + + DECLARE @SeparatorWithoutCursor CHAR(1) = ''; + DECLARE @SeparatorWithCursor CHAR(1) = ''; + DECLARE @ParamName sysname; + DECLARE @TypeName sysname; + DECLARE @IsOutput BIT; + DECLARE @IsCursorRef BIT; + DECLARE @IsTableType BIT; - DECLARE @Separator CHAR(1), - @ProcParmTypeListSeparator CHAR(1), - @ParamName sysname, - @TypeName sysname, - @IsOutput BIT, - @IsCursorRef BIT, - @IsTableType BIT; - - SELECT @Separator = '', @ProcParmTypeListSeparator = '', - @ProcParmList = '', @TableColList = '', @ProcParmTypeList = '', @TableColTypeList = ''; - DECLARE Parameters CURSOR FOR SELECT p.name, t.TypeName, p.is_output, p.is_cursor_ref, t.IsTableType FROM sys.parameters p @@ -39,13 +39,13 @@ BEGIN BEGIN IF @IsCursorRef = 0 BEGIN - SELECT @ProcParmList = @ProcParmList + @Separator + + SELECT @ProcParmListForInsert = @ProcParmListForInsert + @SeparatorWithoutCursor + CASE WHEN @IsTableType = 1 THEN '(SELECT * FROM '+@ParamName+' FOR XML PATH(''row''),TYPE,ROOT('''+STUFF(@ParamName,1,1,'')+'''))' ELSE @ParamName END, - @TableColList = @TableColList + @Separator + '[' + STUFF(@ParamName,1,1,'') + ']', - @ProcParmTypeList = @ProcParmTypeList + @ProcParmTypeListSeparator + @ParamName + ' ' + @TypeName + + @TableColList = @TableColList + @SeparatorWithoutCursor + '[' + STUFF(@ParamName,1,1,'') + ']', + @ProcParmTypeList = @ProcParmTypeList + @SeparatorWithCursor + @ParamName + ' ' + @TypeName + CASE WHEN @IsTableType = 1 THEN ' READONLY' ELSE ' = NULL ' END+ CASE WHEN @IsOutput = 1 THEN ' OUT' ELSE '' END, @TableColTypeList = @TableColTypeList + ',[' + STUFF(@ParamName,1,1,'') + '] ' + @@ -60,15 +60,21 @@ BEGIN ELSE @TypeName END + ' NULL'; - SELECT @Separator = ','; - SELECT @ProcParmTypeListSeparator = ','; + SELECT @SeparatorWithoutCursor = ','; END ELSE BEGIN - SELECT @ProcParmTypeList = @ProcParmTypeListSeparator + @ParamName + ' CURSOR VARYING OUTPUT'; - SELECT @ProcParmTypeListSeparator = ','; + SELECT @ProcParmTypeList = @ProcParmTypeList + @SeparatorWithCursor + @ParamName + ' CURSOR VARYING OUTPUT'; END; - + SELECT + @ProcParmListForCall = @ProcParmListForCall + @SeparatorWithCursor + @ParamName + + CASE + WHEN @IsOutput = 1 AND @IsCursorRef <> 1 + THEN ' OUT' + ELSE '' + END; + SELECT @SeparatorWithCursor = ','; + FETCH NEXT FROM Parameters INTO @ParamName, @TypeName, @IsOutput, @IsCursorRef, @IsTableType; END; @@ -78,7 +84,7 @@ BEGIN DECLARE @InsertStmt NVARCHAR(MAX); SELECT @InsertStmt = 'INSERT INTO ' + @LogTableName + CASE WHEN @TableColList = '' THEN ' DEFAULT VALUES' - ELSE ' (' + @TableColList + ') SELECT ' + @ProcParmList + ELSE ' (' + @TableColList + ') SELECT ' + @ProcParmListForInsert END + ';'; SELECT @CreateLogTableStatement = 'CREATE TABLE ' + @LogTableName + ' (_id_ int IDENTITY(1,1) PRIMARY KEY CLUSTERED ' + @TableColTypeList + ');'; @@ -87,9 +93,16 @@ BEGIN 'CREATE PROCEDURE ' + @OriginalProcedureName + ' ' + @ProcParmTypeList + ' AS BEGIN ' + ISNULL(@InsertStmt,'') + + ISNULL('DECLARE @SpyProcedureOriginalObjectName NVARCHAR(MAX) = '''+REPLACE(QUOTENAME(OBJECT_SCHEMA_NAME(@ProcedureObjectId))+'.'+QUOTENAME(@UnquotedNewNameOfProcedure),'''','''''')+''';','')+ ISNULL(@CommandToExecute + ';', '') + + CHAR(13)+CHAR(10)+/*CR,LF*/ + CASE WHEN @CallOriginal = 1 + THEN 'EXEC @SpyProcedureOriginalObjectName ' + @ProcParmListForCall + ';' + ELSE '' + END + ' RETURN;' + ' END;'; + --RAISERROR(@CreateProcedureStatement, 0, 1) WITH NOWAIT; RETURN; END; diff --git a/Source/tSQLt.Private_GetDropItemCmd.sfn.sql b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql index 7148c9929..bd6d6ef8d 100644 --- a/Source/tSQLt.Private_GetDropItemCmd.sfn.sql +++ b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql @@ -14,8 +14,13 @@ AS RETURN /*SnipStart: CreateDropClassStatement.ps1*/ SELECT + CASE @ItemType + WHEN 'F' THEN 'ALTER TABLE '+(SELECT QUOTENAME(SCHEMA_NAME(schema_id))+'.'+QUOTENAME(OBJECT_NAME(parent_object_id)) FROM sys.objects WHERE OBJECT_ID = OBJECT_ID(@FullName))+' ' + ELSE '' + END+ 'DROP ' + CASE @ItemType + WHEN 'F' THEN 'CONSTRAINT' WHEN 'IF' THEN 'FUNCTION' WHEN 'TF' THEN 'FUNCTION' WHEN 'FN' THEN 'FUNCTION' @@ -30,7 +35,10 @@ SELECT WHEN 'schema' THEN 'SCHEMA' END+ ' ' + - @FullName + + CASE @ItemType + WHEN 'F' THEN QUOTENAME(OBJECT_NAME(OBJECT_ID(@FullName))) + ELSE @FullName + END+ ';' AS cmd /*SnipEnd: CreateDropClassStatement.ps1*/ GO @@ -40,7 +48,7 @@ Object type: AF = Aggregate function (CLR) - C = CHECK constraint - D = DEFAULT (constraint or stand-alone) -- F = FOREIGN KEY constraint ++ F = FOREIGN KEY constraint + FN = SQL scalar function FS = Assembly (CLR) scalar-function + FT = Assembly (CLR) table-valued function diff --git a/Source/tSQLt.Private_GetFormattedErrorInfo.sfn.sql b/Source/tSQLt.Private_GetFormattedErrorInfo.sfn.sql new file mode 100644 index 000000000..bb38fd081 --- /dev/null +++ b/Source/tSQLt.Private_GetFormattedErrorInfo.sfn.sql @@ -0,0 +1,17 @@ +IF OBJECT_ID('tSQLt.Private_GetFormattedErrorInfo') IS NOT NULL DROP FUNCTION tSQLt.Private_GetFormattedErrorInfo; +GO +---Build+ +CREATE FUNCTION tSQLt.Private_GetFormattedErrorInfo() +RETURNS TABLE +AS +RETURN + SELECT 'Message: ' + ISNULL(ERROR_MESSAGE(),'') + ' | Procedure: ' + ISNULL(ERROR_PROCEDURE(),'') + ISNULL(' (' + CAST(ERROR_LINE() AS NVARCHAR(MAX)) + ')','') + ' | Severity, State: ' + ISNULL(CAST(ERROR_SEVERITY() AS NVARCHAR(MAX)),'') + ', ' + ISNULL(CAST(ERROR_STATE() AS NVARCHAR(MAX)), '') + ' | Number: ' + ISNULL(CAST(ERROR_NUMBER() AS NVARCHAR(MAX)), '') AS FormattedError; +GO +---Build- +GO +BEGIN TRY + SELECT CAST('A' AS INT); +END TRY +BEGIN CATCH + SELECT FormattedError FROM tSQLt.Private_GetFormattedErrorInfo(); +END CATCH \ No newline at end of file diff --git a/Source/tSQLt.Private_HandleMessageAndResult.sfn.sql b/Source/tSQLt.Private_HandleMessageAndResult.sfn.sql new file mode 100644 index 000000000..2379fa0e5 --- /dev/null +++ b/Source/tSQLt.Private_HandleMessageAndResult.sfn.sql @@ -0,0 +1,22 @@ +IF OBJECT_ID('tSQLt.Private_HandleMessageAndResult') IS NOT NULL DROP FUNCTION tSQLt.Private_HandleMessageAndResult; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_HandleMessageAndResult ( + @PrevMessage NVARCHAR(MAX), + @PrevResult NVARCHAR(MAX), + @NewMessage NVARCHAR(MAX), + @NewResult NVARCHAR(MAX) +) +RETURNS TABLE +AS +RETURN + SELECT CASE WHEN ISNULL(@PrevMessage,'') NOT LIKE '%[^ '+CHAR(9)+']%' AND @PrevResult = 'Success' THEN '' + ELSE CASE WHEN @PrevMessage NOT LIKE '%[^ '+CHAR(9)+']%' THEN '' ELSE ISNULL(@PrevMessage,'') END+' [Result: '+ + CASE WHEN @PrevResult NOT LIKE '%[^ '+CHAR(9)+']%' THEN '' ELSE ISNULL(@PrevResult,'') END+'] || ' + END+ + CASE WHEN @NewMessage NOT LIKE '%[^ '+CHAR(9)+']%' THEN '' ELSE ISNULL(@NewMessage,'') END Message, + (SELECT TOP(1) Result FROM tSQLt.Private_Results WHERE Result IN (@PrevResult, @NewResult) ORDER BY Severity DESC) Result; +GO +---Build- +GO diff --git a/Source/tSQLt.Private_Init.ssp.sql b/Source/tSQLt.Private_Init.ssp.sql index ddf1efb2c..4190cc741 100644 --- a/Source/tSQLt.Private_Init.ssp.sql +++ b/Source/tSQLt.Private_Init.ssp.sql @@ -7,8 +7,6 @@ AS BEGIN EXEC tSQLt.Private_CleanTestResult; - UPDATE tSQLt.Private_NullCellTable SET I=I WHERE 'Forcing trigger execution.' != ''; - DECLARE @enable BIT; SET @enable = 1; DECLARE @version_match BIT;SET @version_match = 0; BEGIN TRY @@ -19,7 +17,7 @@ BEGIN RETURN; END CATCH; SELECT @version_match = CASE WHEN I.SqlVersion = I.InstalledOnSqlVersion THEN 1 ELSE 0 END FROM tSQLt.Info() AS I WHERE @version_match = 1; - IF(@version_match = 0) + IF(@version_match = 0 OR EXISTS(SELECT 1 FROM tSQLt.Private_Seize)) BEGIN RAISERROR('tSQLt is in an invalid state. Please reinstall tSQLt.',16,10); RETURN; diff --git a/Source/tSQLt.Private_Lock.tbl.sql b/Source/tSQLt.Private_Lock.tbl.sql new file mode 100644 index 000000000..855337854 --- /dev/null +++ b/Source/tSQLt.Private_Lock.tbl.sql @@ -0,0 +1,7 @@ +IF OBJECT_ID('tSQLt.Private_Lock') IS NOT NULL DROP TABLE tSQLt.Private_Lock; +GO +--DECLARE @Lock INT = (SELECT COUNT(1) FROM tSQLt.Lock AS L WITH(TABLOCKX)); +---Build+ +GO +CREATE TABLE tSQLt.Private_Lock(X BIT CONSTRAINT [PK:tSQLt.Private_Lock] PRIMARY KEY CONSTRAINT [CHK:tSQLt.Private_Lock(X)] CHECK(X=13)); +GO diff --git a/Source/tSQLt.Private_MarktSQLtTempObject.ssp.sql b/Source/tSQLt.Private_MarktSQLtTempObject.ssp.sql index f05a2af81..fa12ef231 100644 --- a/Source/tSQLt.Private_MarktSQLtTempObject.ssp.sql +++ b/Source/tSQLt.Private_MarktSQLtTempObject.ssp.sql @@ -4,7 +4,7 @@ GO CREATE PROCEDURE tSQLt.Private_MarktSQLtTempObject @ObjectName NVARCHAR(MAX), @ObjectType NVARCHAR(MAX), - @NewNameOfOriginalObject NVARCHAR(4000) + @NewNameOfOriginalObject NVARCHAR(4000) = NULL AS BEGIN DECLARE @UnquotedSchemaName NVARCHAR(MAX); @@ -26,11 +26,14 @@ BEGIN @level0type = N'SCHEMA', @level0name = @UnquotedSchemaName, @level1type = @ObjectType, @level1name = @UnquotedObjectName; - EXEC sys.sp_addextendedproperty - @name = N'tSQLt.Private_TestDouble_OrgObjectName', - @value = @NewNameOfOriginalObject, - @level0type = N'SCHEMA', @level0name = @UnquotedSchemaName, - @level1type = @ObjectType, @level1name = @UnquotedObjectName; + IF(@NewNameOfOriginalObject IS NOT NULL) + BEGIN + EXEC sys.sp_addextendedproperty + @name = N'tSQLt.Private_TestDouble_OrgObjectName', + @value = @NewNameOfOriginalObject, + @level0type = N'SCHEMA', @level0name = @UnquotedSchemaName, + @level1type = @ObjectType, @level1name = @UnquotedObjectName; + END; END; ELSE BEGIN @@ -41,12 +44,15 @@ BEGIN @level1type = N'TABLE', @level1name = @UnquotedParentName, @level2type = @ObjectType, @level2name = @UnquotedObjectName; - EXEC sys.sp_addextendedproperty - @name = N'tSQLt.Private_TestDouble_OrgObjectName', - @value = @NewNameOfOriginalObject, - @level0type = N'SCHEMA', @level0name = @UnquotedSchemaName, - @level1type = N'TABLE', @level1name = @UnquotedParentName, - @level2type = @ObjectType, @level2name = @UnquotedObjectName; + IF(@NewNameOfOriginalObject IS NOT NULL) + BEGIN + EXEC sys.sp_addextendedproperty + @name = N'tSQLt.Private_TestDouble_OrgObjectName', + @value = @NewNameOfOriginalObject, + @level0type = N'SCHEMA', @level0name = @UnquotedSchemaName, + @level1type = N'TABLE', @level1name = @UnquotedParentName, + @level2type = @ObjectType, @level2name = @UnquotedObjectName; + END; END; END; ---Build- diff --git a/Source/tSQLt.Private_NoTransactionHandleTable.ssp.sql b/Source/tSQLt.Private_NoTransactionHandleTable.ssp.sql new file mode 100644 index 000000000..2eac91c4b --- /dev/null +++ b/Source/tSQLt.Private_NoTransactionHandleTable.ssp.sql @@ -0,0 +1,104 @@ +IF OBJECT_ID('tSQLt.Private_NoTransactionHandleTable') IS NOT NULL DROP PROCEDURE tSQLt.Private_NoTransactionHandleTable; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_NoTransactionHandleTable +@Action NVARCHAR(MAX), +@FullTableName NVARCHAR(MAX), +@TableAction NVARCHAR(MAX) +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX); + BEGIN TRY + IF (OBJECT_ID(@FullTableName) IS NULL AND @TableAction <> 'Hide') + BEGIN + RAISERROR('Table %s does not exist.',16,10,@FullTableName); + END; + IF (@Action = 'Save') + BEGIN + IF (@TableAction = 'Restore') + BEGIN + IF(NOT EXISTS(SELECT 1 FROM #TableBackupLog TBL WHERE TBL.OriginalName = @FullTableName)) + BEGIN + DECLARE @NewQuotedNameForBackupTable NVARCHAR(MAX) = '[tSQLt].'+QUOTENAME(tSQLt.Private::CreateUniqueObjectName()); + SET @cmd = 'SELECT * INTO '+@NewQuotedNameForBackupTable+' FROM '+@FullTableName+';'; + EXEC (@Cmd); + INSERT INTO #TableBackupLog (OriginalName, BackupName) VALUES (@FullTableName, @NewQuotedNameForBackupTable); + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = @NewQuotedNameForBackupTable, @ObjectType = N'TABLE', @NewNameOfOriginalObject = NULL; + END; + END; + ELSE IF (@TableAction = 'Hide') + BEGIN + IF (NOT EXISTS (SELECT 1 FROM tSQLt.Private_RenamedObjectLog ROL WHERE QUOTENAME(OBJECT_SCHEMA_NAME(ROL.ObjectId))+'.'+OriginalName = @FullTableName)) + BEGIN + IF(OBJECT_ID(@FullTableName) IS NULL) + BEGIN + RAISERROR('Table %s does not exist.',16,10,@FullTableName); + END; + EXEC tSQLt.RemoveObject @ObjectName = @FullTableName; + END; + END; + ELSE IF (@TableAction IN ('Truncate', 'Ignore')) + BEGIN + RETURN; + END; + ELSE + BEGIN + RAISERROR('Invalid @TableAction parameter value.',16,10); + END; + END; + ELSE IF (@Action = 'Reset') + BEGIN + IF (@TableAction = 'Restore') + BEGIN + BEGIN TRAN; + DECLARE @BackupTableName TABLE(TableName NVARCHAR(MAX)); + DELETE FROM #TableBackupLog OUTPUT DELETED.BackupName INTO @BackupTableName WHERE OriginalName = @FullTableName; + IF(EXISTS(SELECT 1 FROM @BackupTableName AS BTN)) + BEGIN + SET @cmd = 'DELETE FROM ' + @FullTableName + ';'; + IF (EXISTS(SELECT 1 FROM sys.columns WHERE object_id = OBJECT_ID(@FullTableName) AND is_identity = 1)) + BEGIN + SET @cmd = @cmd + 'SET IDENTITY_INSERT ' + @FullTableName + ' ON;'; + END; + SET @cmd = @cmd + 'INSERT INTO ' + @FullTableName +'('; + DECLARE @ColumnList NVARCHAR(MAX) = STUFF((SELECT ','+QUOTENAME(name) FROM sys.columns WHERE object_id = OBJECT_ID(@FullTableName) AND is_computed = 0 ORDER BY column_id FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,''); + SET @cmd = @cmd + @ColumnList; + SET @cmd = @cmd + ') SELECT ' + @ColumnList + ' FROM ' + (SELECT TableName FROM @BackupTableName)+';'; + EXEC(@cmd); + END; + COMMIT; + END; + ELSE IF (@TableAction = 'Truncate') + BEGIN + EXEC('DELETE FROM ' + @FullTableName +';'); + END; + ELSE IF (@TableAction IN ('Ignore','Hide')) + BEGIN + /* Hidden tables will be restored by UndoTestDoubles. */ + RETURN; + END; + ELSE + BEGIN + RAISERROR('Invalid @TableAction parameter value.', 16, 10); + END; + END; + ELSE + BEGIN + RAISERROR('Invalid @Action parameter value.',16,10); + END; + END TRY + BEGIN CATCH + DECLARE @ErrorLine INT = ERROR_LINE(); + DECLARE @ErrorProcedure NVARCHAR(MAX) = ERROR_PROCEDURE(); + DECLARE @ErrorMessage NVARCHAR(MAX) = ERROR_MESSAGE(); + DECLARE @ErrorSeverity INT = ERROR_SEVERITY(); + DECLARE @ErrorState INT = ERROR_STATE(); + RAISERROR('tSQLt is in an unknown state: Stopping execution. (%s | Procedure: %s | Line: %i)', @ErrorSeverity, @ErrorState, @ErrorMessage, @ErrorProcedure, @ErrorLine); + END CATCH; +END; +GO +--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX-- +--DECLARE @TempMsg58 NVARCHAR(MAX) = FORMATMESSAGE('HandleTable(58) - @BackupTableName = %s, @FullTableName = %s, XACT_STATE = %i, SummaryError = %i',(SELECT TableName FROM @BackupTableName), @FullTableName, XACT_STATE(), CAST((SELECT PGC.Value FROM tSQLt.Private_GetConfiguration('SummaryError') AS PGC) AS INT));RAISERROR(@TempMsg58, 0,1) WITH NOWAIT; +--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX--XX-- + diff --git a/Source/tSQLt.Private_NoTransactionHandleTables.ssp.sql b/Source/tSQLt.Private_NoTransactionHandleTables.ssp.sql new file mode 100644 index 000000000..87a8af3c9 --- /dev/null +++ b/Source/tSQLt.Private_NoTransactionHandleTables.ssp.sql @@ -0,0 +1,17 @@ +IF OBJECT_ID('tSQLt.Private_NoTransactionHandleTables') IS NOT NULL DROP PROCEDURE tSQLt.Private_NoTransactionHandleTables; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_NoTransactionHandleTables + @Action NVARCHAR(MAX) +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX) = ( + SELECT 'EXEC tSQLt.Private_NoTransactionHandleTable @Action = '''+@Action+''', @FullTableName = '''+X.Name+''', @TableAction = '''+X.Action+''';' + FROM tSQLt.Private_NoTransactionTableAction X + WHERE X.Action <> 'Ignore' + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + EXEC(@cmd); +END; +GO \ No newline at end of file diff --git a/Source/tSQLt.Private_NoTransactionTableAction.view.sql b/Source/tSQLt.Private_NoTransactionTableAction.view.sql new file mode 100644 index 000000000..3b80a0673 --- /dev/null +++ b/Source/tSQLt.Private_NoTransactionTableAction.view.sql @@ -0,0 +1,18 @@ +IF OBJECT_ID('tSQLt.Private_NoTransactionTableAction') IS NOT NULL DROP VIEW tSQLt.Private_NoTransactionTableAction; +GO +---Build+ +GO +CREATE VIEW tSQLt.Private_NoTransactionTableAction +AS +SELECT CAST(Name AS NVARCHAR(MAX)) Name, CAST(Action AS NVARCHAR(MAX)) Action + FROM( + VALUES('[tSQLt].[Private_NewTestClassList]','Hide'), + ('[tSQLt].[Run_LastExecution]','Hide'), + ('[tSQLt].[Private_Configurations]','Restore'), + ('[tSQLt].[CaptureOutputLog]','Truncate'), + ('[tSQLt].[Private_RenamedObjectLog]','Ignore'), + ('[tSQLt].[Private_Seize]','Ignore'), + ('[tSQLt].[Private_Seize_NoTruncate]','Ignore'), + ('[tSQLt].[TestResult]','Restore') + )X(Name, Action); +GO diff --git a/Source/tSQLt.Private_NullCellTable.tbl.sql b/Source/tSQLt.Private_NullCellTable.tbl.sql deleted file mode 100644 index 8aea4a562..000000000 --- a/Source/tSQLt.Private_NullCellTable.tbl.sql +++ /dev/null @@ -1,21 +0,0 @@ -IF OBJECT_ID('tSQLt.Private_NullCellTable_PreventTruncate') IS NOT NULL DROP TABLE tSQLt.Private_NullCellTable_PreventTruncate; -IF OBJECT_ID('tSQLt.Private_NullCellTable') IS NOT NULL DROP TABLE tSQLt.Private_NullCellTable; -GO ----Build+ -GO -CREATE TABLE tSQLt.Private_NullCellTable( - I INT CONSTRAINT[U:tSQLt.Private_NullCellTable] UNIQUE CLUSTERED -); -GO - -INSERT INTO tSQLt.Private_NullCellTable (I) VALUES (NULL); -GO - -CREATE TRIGGER tSQLt.Private_NullCellTable_StopModifications ON tSQLt.Private_NullCellTable INSTEAD OF DELETE, INSERT, UPDATE -AS -BEGIN - IF EXISTS (SELECT 1 FROM tSQLt.Private_NullCellTable) RETURN; - INSERT INTO tSQLt.Private_NullCellTable VALUES (NULL); -END; -GO - diff --git a/Source/tSQLt.Private_RenameObject.ssp.sql b/Source/tSQLt.Private_RenameObject.ssp.sql index e464b7a52..7dd62db87 100644 --- a/Source/tSQLt.Private_RenameObject.ssp.sql +++ b/Source/tSQLt.Private_RenameObject.ssp.sql @@ -9,8 +9,8 @@ AS BEGIN DECLARE @RenameCmd NVARCHAR(MAX); SET @RenameCmd = 'EXEC sp_rename ''' + - @SchemaName + '.' + @ObjectName + ''', ''' + - @NewName + ''',''OBJECT'';'; + REPLACE(@SchemaName + '.' + @ObjectName, '''', '''''') + ''', ''' + + REPLACE(@NewName, '''', '''''') + ''',''OBJECT'';'; EXEC tSQLt.SuppressOutput @RenameCmd; END; diff --git a/Source/tSQLt.Private_RenameObjectToUniqueName.ssp.sql b/Source/tSQLt.Private_RenameObjectToUniqueName.ssp.sql index d34ec79fe..e50bf7745 100644 --- a/Source/tSQLt.Private_RenameObjectToUniqueName.ssp.sql +++ b/Source/tSQLt.Private_RenameObjectToUniqueName.ssp.sql @@ -7,7 +7,7 @@ CREATE PROCEDURE tSQLt.Private_RenameObjectToUniqueName @NewName NVARCHAR(MAX) = NULL OUTPUT AS BEGIN - SET @NewName=tSQLt.Private::CreateUniqueObjectName(); + SET @NewName=ISNULL(@NewName, tSQLt.Private::CreateUniqueObjectName()); EXEC tSQLt.Private_MarkObjectBeforeRename @SchemaName, @ObjectName; diff --git a/Source/tSQLt.Private_Results.view.sql b/Source/tSQLt.Private_Results.view.sql new file mode 100644 index 000000000..4a628284d --- /dev/null +++ b/Source/tSQLt.Private_Results.view.sql @@ -0,0 +1,18 @@ +IF OBJECT_ID('tSQLt.Private_Results') IS NOT NULL DROP VIEW tSQLt.Private_Results; +GO +---Build+ +GO +CREATE VIEW tSQLt.Private_Results +AS +SELECT CAST(Severity AS INT) Severity,CAST(Result AS NVARCHAR(MAX)) Result + FROM( + VALUES(1, 'Success') + , + (2, 'Skipped'), + (3, 'Failure'), + (4, 'Error'), + (5, 'Abort'), + (6, 'FATAL') + )X(Severity, Result); +GO + diff --git a/Source/tSQLt.Private_Seize.tbl.sql b/Source/tSQLt.Private_Seize.tbl.sql new file mode 100644 index 000000000..eb17bb3d7 --- /dev/null +++ b/Source/tSQLt.Private_Seize.tbl.sql @@ -0,0 +1,21 @@ +IF OBJECT_ID('tSQLt.Private_Seize_NoTruncate') IS NOT NULL DROP TABLE tSQLt.Private_Seize_NoTruncate; +IF OBJECT_ID('tSQLt.Private_Seize') IS NOT NULL DROP TABLE tSQLt.Private_Seize; +GO +---Build+ +GO +CREATE TABLE tSQLt.Private_Seize( + Kaput BIT CONSTRAINT [Private_Seize:PK] PRIMARY KEY CONSTRAINT [Private_Seize:CHK] CHECK(Kaput=1) +); +GO +CREATE TABLE tSQLt.Private_Seize_NoTruncate( + NoTruncate BIT CONSTRAINT [Private_Seize_NoTruncate(NoTruncate):FK] FOREIGN KEY REFERENCES tSQLt.Private_Seize(Kaput) +); +GO +CREATE TRIGGER tSQLt.Private_Seize_Stop ON tSQLt.Private_Seize INSTEAD OF DELETE,UPDATE +AS +BEGIN + RAISERROR('This is a private table that you should not mess with!',16,10); +END; +GO + + \ No newline at end of file diff --git a/Source/tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure.ssp.sql b/Source/tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure.ssp.sql new file mode 100644 index 000000000..608e394ed --- /dev/null +++ b/Source/tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure.ssp.sql @@ -0,0 +1,21 @@ +IF OBJECT_ID('tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure') IS NOT NULL DROP PROCEDURE tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure + @ProcedureName NVARCHAR(MAX) +AS +BEGIN + IF NOT EXISTS(SELECT 1 FROM sys.procedures WHERE object_id = OBJECT_ID(@ProcedureName)) + BEGIN + RAISERROR('Cannot use SpyProcedure on %s because the procedure does not exist', 16, 10, @ProcedureName) WITH NOWAIT; + END; + + IF (1020 < (SELECT COUNT(*) FROM sys.parameters WHERE object_id = OBJECT_ID(@ProcedureName))) + BEGIN + RAISERROR('Cannot use SpyProcedure on procedure %s because it contains more than 1020 parameters', 16, 10, @ProcedureName) WITH NOWAIT; + END; +END; +GO + + diff --git a/Source/tSQLt.SpyProcedure.ssp.sql b/Source/tSQLt.SpyProcedure.ssp.sql index 4740b47f9..ea05ed842 100644 --- a/Source/tSQLt.SpyProcedure.ssp.sql +++ b/Source/tSQLt.SpyProcedure.ssp.sql @@ -3,7 +3,8 @@ GO ---Build+ CREATE PROCEDURE tSQLt.SpyProcedure @ProcedureName NVARCHAR(MAX), - @CommandToExecute NVARCHAR(MAX) = NULL + @CommandToExecute NVARCHAR(MAX) = NULL, + @CallOriginal BIT = 0 AS BEGIN DECLARE @ProcedureObjectId INT; @@ -17,23 +18,30 @@ BEGIN DECLARE @CreateProcedureStatement NVARCHAR(MAX); DECLARE @CreateLogTableStatement NVARCHAR(MAX); + DECLARE @NewNameOfOriginalObject NVARCHAR(MAX) = tSQLt.Private::CreateUniqueObjectName(); + EXEC tSQLt.Private_GenerateCreateProcedureSpyStatement @ProcedureObjectId = @ProcedureObjectId, @OriginalProcedureName = @ProcedureName, + @UnquotedNewNameOfProcedure = @NewNameOfOriginalObject, @LogTableName = @LogTableName, @CommandToExecute = @CommandToExecute, + @CallOriginal = @CallOriginal, @CreateProcedureStatement = @CreateProcedureStatement OUT, @CreateLogTableStatement = @CreateLogTableStatement OUT; - - DECLARE @NewNameOfOriginalObject NVARCHAR(MAX); - - EXEC tSQLt.Private_RenameObjectToUniqueNameUsingObjectId @ProcedureObjectId, @NewName = @NewNameOfOriginalObject OUTPUT; + DECLARE @LogTableObjectId INT = OBJECT_ID(@LogTableName); + IF(@LogTableObjectId IS NOT NULL) + BEGIN + EXEC tSQLt.Private_RenameObjectToUniqueNameUsingObjectId @ObjectId = @LogTableObjectId; + END; EXEC(@CreateLogTableStatement); + EXEC tSQLt.Private_RenameObjectToUniqueNameUsingObjectId @ProcedureObjectId, @NewName = @NewNameOfOriginalObject OUTPUT; EXEC(@CreateProcedureStatement); EXEC tSQLt.Private_MarktSQLtTempObject @ProcedureName, N'PROCEDURE', @NewNameOfOriginalObject; + EXEC tSQLt.Private_MarktSQLtTempObject @LogTableName, N'TABLE', NULL; RETURN 0; END; diff --git a/Source/tSQLt.UndoTestDoubles.ssp.sql b/Source/tSQLt.UndoTestDoubles.ssp.sql index 050e7c84e..59a7bb63a 100644 --- a/Source/tSQLt.UndoTestDoubles.ssp.sql +++ b/Source/tSQLt.UndoTestDoubles.ssp.sql @@ -7,17 +7,12 @@ CREATE PROCEDURE tSQLt.UndoTestDoubles AS BEGIN DECLARE @cmd NVARCHAR(MAX); + DECLARE @ErrorMessageTableList NVARCHAR(MAX); + DECLARE @ErrorMessage NVARCHAR(MAX) = ''; - IF (@Force = 1) - BEGIN - SET @cmd = 'EXEC tSQLt.Private_Print @Message = ''WARNING: @Force has been set to 1. Dropping the following objects that are not marked as temporary. (%s)'';'; - END; - ELSE - BEGIN - SET @cmd = 'RAISERROR(''Cannot drop these objects as they are not marked as temporary. Use @Force = 1 to override. (%s)'',16,10)'; - END; - SELECT @cmd = REPLACE(@cmd,'%s',Collisions.List) + /*-- Two non-temp objects, the first of which should be renamed to the second --*/ + SELECT @ErrorMessage = @ErrorMessage + ISNULL(REPLACE('Attempting to remove object(s) that is/are not marked as temporary. Use @Force = 1 to override. (%s)','%s',Collisions.List),'') FROM ( SELECT @@ -39,7 +34,92 @@ BEGIN ).value('.','NVARCHAR(MAX)'), 1,2,'') ) Collisions(List) - EXEC(@cmd); + + /*-- Attempting to rename two or more non-temp objects to the same name --*/ + + IF(EXISTS( + SELECT O.schema_id, ROL.OriginalName, COUNT(1) cnt + FROM tSQLt.Private_RenamedObjectLog ROL + JOIN sys.objects O + ON ROL.ObjectId = O.object_id + LEFT JOIN sys.extended_properties AS EP + ON EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = O.object_id + AND EP.name = 'tSQLt.IsTempObject' + AND EP.value = 1 + WHERE EP.value IS NULL + GROUP BY O.schema_id, ROL.OriginalName + HAVING COUNT(1)>1 + )) + BEGIN + WITH S AS( + SELECT + C.Id, + C.OriginalName, + C.CurrentName, + C.SchemaName + FROM( + SELECT ROL.OriginalName, ROL.Id, O.name CurrentName, SCHEMA_NAME(O.schema_id) SchemaName, COUNT(1)OVER(PARTITION BY O.schema_id, ROL.OriginalName) Cnt + FROM tSQLt.Private_RenamedObjectLog ROL + JOIN sys.objects O + ON ROL.ObjectId = O.object_id + LEFT JOIN sys.extended_properties AS EP + ON EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = O.object_id + AND EP.name = 'tSQLt.IsTempObject' + AND EP.value = 1 + WHERE EP.value IS NULL + )C + WHERE C.Cnt>1 + ), + ErrorTableLists AS( + SELECT + '{'+C.CList+'}-->' + QUOTENAME(SO.SchemaName)+'.'+QUOTENAME(PARSENAME(SO.OriginalName,1)) ErrorTableList, + QUOTENAME(SO.SchemaName)+'.'+QUOTENAME(PARSENAME(SO.OriginalName,1)) FullOriginalName + FROM (SELECT DISTINCT SchemaName, OriginalName FROM S) SO + CROSS APPLY ( + SELECT ( + STUFF( + ( + SELECT ', '+QUOTENAME(SC.CurrentName) + FROM S AS SC + WHERE SC.OriginalName = SO.OriginalName + AND SC.SchemaName = SO.SchemaName + ORDER BY SC.Id + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'), + 1,2,'') + ) CList + )C + ) + SELECT @ErrorMessageTableList = ( + STUFF( + ( + SELECT '; '+ETL.ErrorTableList + FROM ErrorTableLists ETL + ORDER BY ETL.FullOriginalName + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'), + 1,2,'' + ) + ); + SELECT @ErrorMessage = @ErrorMessage + REPLACE('Attempting to rename two or more objects to the same name. Use @Force = 1 to override, only first object of each rename survives. (%s)','%s',@ErrorMessageTableList); + END; + IF(@ErrorMessage <> '') + BEGIN + IF (@Force = 1) + BEGIN + SET @ErrorMessage = 'WARNING: @Force has been set to 1. Overriding the following error(s):'+@ErrorMessage; + EXEC tSQLt.Private_Print @Message = @ErrorMessage; + END; + ELSE + BEGIN + RAISERROR(@ErrorMessage,16,10); + END; + END; + + + SELECT TOP(0)A.* INTO #RenamedObjects FROM tSQLt.Private_RenamedObjectLog A RIGHT JOIN tSQLt.Private_RenamedObjectLog X ON 1=0; @@ -49,6 +129,57 @@ BEGIN BEGIN TRAN; DELETE FROM tSQLt.Private_RenamedObjectLog OUTPUT Deleted.* INTO #RenamedObjects; + + WITH MarkedTestDoubles AS + ( + SELECT + TempO.Name, + SCHEMA_NAME(TempO.schema_id) SchemaName, + TempO.type ObjectType + FROM sys.objects TempO + JOIN sys.extended_properties AS EP + ON EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = TempO.object_id + AND EP.name = 'tSQLt.IsTempObject' + AND EP.value = 1 + ) + SELECT @cmd = + ( + SELECT + DC.cmd+';' + FROM MarkedTestDoubles MTD + CROSS APPLY tSQLt.Private_GetDropItemCmd(QUOTENAME(MTD.SchemaName)+'.'+QUOTENAME(MTD.Name),MTD.ObjectType) DC + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + EXEC(@cmd); + + SELECT @cmd = + ( + SELECT + DC.cmd+';' + FROM( + SELECT + * + FROM( + SELECT + ROL.OriginalName, + O.object_id, + O.type ObjectType, + SCHEMA_NAME(O.schema_id) SchemaName, + O.name CurrentName, + ROW_NUMBER()OVER(PARTITION BY O.schema_id, ROL.OriginalName ORDER BY ROL.Id) RN + FROM #RenamedObjects AS ROL + JOIN sys.objects O + ON O.object_id = ROL.ObjectId + )ROLI + WHERE ROLI.RN>1 + )Deletables + CROSS APPLY tSQLt.Private_GetDropItemCmd(QUOTENAME(Deletables.SchemaName)+'.'+QUOTENAME(Deletables.CurrentName),Deletables.ObjectType) DC + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + EXEC(@cmd); + + WITH LL AS ( SELECT @@ -98,31 +229,9 @@ BEGIN CROSS APPLY tSQLt.Private_GetDropItemCmd(QUOTENAME(L.SchemaName)+'.'+QUOTENAME(L.OriginalName),L.ObjectType) DC ORDER BY L.SortId DESC, L.Id ASC FOR XML PATH(''),TYPE - ).value('.','NVARCHAR(MAX)') + ).value('.','NVARCHAR(MAX)'); EXEC(@cmd); - WITH L AS - ( - SELECT - TempO.Name, - SCHEMA_NAME(TempO.schema_id) SchemaName, - TempO.type ObjectType - FROM sys.objects TempO - JOIN sys.extended_properties AS EP - ON EP.class_desc = 'OBJECT_OR_COLUMN' - AND EP.major_id = TempO.object_id - AND EP.name = 'tSQLt.IsTempObject' - AND EP.value = 1 - ) - SELECT @cmd = - ( - SELECT - DC.cmd+';' - FROM L - CROSS APPLY tSQLt.Private_GetDropItemCmd(QUOTENAME(L.SchemaName)+'.'+QUOTENAME(L.Name),L.ObjectType) DC - FOR XML PATH(''),TYPE - ).value('.','NVARCHAR(MAX)') - EXEC(@cmd); COMMIT; END; diff --git a/Source/tSQLt.Uninstall.ssp.sql b/Source/tSQLt.Uninstall.ssp.sql index fa54e7f76..6ca3d5172 100644 --- a/Source/tSQLt.Uninstall.ssp.sql +++ b/Source/tSQLt.Uninstall.ssp.sql @@ -8,7 +8,7 @@ BEGIN EXEC tSQLt.DropClass @ClassName = 'tSQLt'; - DROP ASSEMBLY tSQLtCLR; + IF(EXISTS(SELECT 1 FROM sys.assemblies WHERE name = 'tSQLtCLR'))DROP ASSEMBLY tSQLtCLR; IF USER_ID('tSQLt.TestClass') IS NOT NULL DROP USER [tSQLt.TestClass]; diff --git a/Source/tSQLt.class.sql b/Source/tSQLt.class.sql index 3e6b92874..cbd5533f3 100644 --- a/Source/tSQLt.class.sql +++ b/Source/tSQLt.class.sql @@ -1,29 +1,23 @@ ---Build+ GO CREATE TABLE tSQLt.TestResult( - Id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED, + Id INT IDENTITY(1,1) CONSTRAINT [PK:tSQLt.TestResult] PRIMARY KEY CLUSTERED, Class NVARCHAR(MAX) NOT NULL, TestCase NVARCHAR(MAX) NOT NULL, Name AS (QUOTENAME(Class) + '.' + QUOTENAME(TestCase)), - TranName NVARCHAR(MAX) NOT NULL, + TranName NVARCHAR(MAX) NULL, Result NVARCHAR(MAX) NULL, Msg NVARCHAR(MAX) NULL, TestStartTime DATETIME2 NOT NULL CONSTRAINT [DF:TestResult(TestStartTime)] DEFAULT SYSDATETIME(), TestEndTime DATETIME2 NULL ); GO -CREATE TABLE tSQLt.TestMessage( - Msg NVARCHAR(MAX) -); -GO CREATE TABLE tSQLt.Run_LastExecution( TestName NVARCHAR(MAX), SessionId INT, LoginTime DATETIME ); GO -CREATE TABLE tSQLt.Private_ExpectException(i INT); -GO CREATE PROCEDURE tSQLt.Private_Print @Message NVARCHAR(MAX), @Severity INT = 0 @@ -127,22 +121,19 @@ GO ---------------------------------------------------------------------- CREATE FUNCTION tSQLt.Private_GetLastTestNameIfNotProvided(@TestName NVARCHAR(MAX)) -RETURNS NVARCHAR(MAX) +RETURNS TABLE AS -BEGIN - IF(LTRIM(ISNULL(@TestName,'')) = '') - BEGIN - SELECT @TestName = TestName - FROM tSQLt.Run_LastExecution le - JOIN sys.dm_exec_sessions es - ON le.SessionId = es.session_id - AND le.LoginTime = es.login_time - WHERE es.session_id = @@SPID; - END - - RETURN @TestName; -END +RETURN + SELECT CASE WHEN (LTRIM(ISNULL(@TestName,'')) = '') THEN LE.TestName ELSE @TestName END TestName + FROM tSQLt.Run_LastExecution LE + RIGHT JOIN sys.dm_exec_sessions ES + ON LE.SessionId = ES.session_id + AND LE.LoginTime = ES.login_time + WHERE ES.session_id = @@SPID; GO +/*-- +IF(LTRIM(ISNULL(@TestName,'')) = '') +--*/ CREATE PROCEDURE tSQLt.Private_SaveTestNameForSession @TestName NVARCHAR(MAX) @@ -182,23 +173,6 @@ RETURN WITH A(Cnt, SuccessCnt, SkippedCnt, FailCnt, ErrorCnt) AS ( FROM A; GO -CREATE PROCEDURE tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure - @ProcedureName NVARCHAR(MAX) -AS -BEGIN - IF NOT EXISTS(SELECT 1 FROM sys.procedures WHERE object_id = OBJECT_ID(@ProcedureName)) - BEGIN - RAISERROR('Cannot use SpyProcedure on %s because the procedure does not exist', 16, 10, @ProcedureName) WITH NOWAIT; - END; - - IF (1020 < (SELECT COUNT(*) FROM sys.parameters WHERE object_id = OBJECT_ID(@ProcedureName))) - BEGIN - RAISERROR('Cannot use SpyProcedure on procedure %s because it contains more than 1020 parameters', 16, 10, @ProcedureName) WITH NOWAIT; - END; -END; -GO - - CREATE PROCEDURE tSQLt.AssertEquals @Expected SQL_VARIANT, @Actual SQL_VARIANT, diff --git a/Source/tSQLtCLR_CreateProcs.sql b/Source/tSQLtCLR_CreateProcs.sql index 7c0a181fc..14aed3ff4 100644 --- a/Source/tSQLtCLR_CreateProcs.sql +++ b/Source/tSQLtCLR_CreateProcs.sql @@ -13,6 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. */ +IF OBJECT_ID('tSQLt.ResultSetFilter') IS NOT NULL DROP PROCEDURE tSQLt.ResultSetFilter; +IF OBJECT_ID('tSQLt.AssertResultSetsHaveSameMetaData') IS NOT NULL DROP PROCEDURE tSQLt.AssertResultSetsHaveSameMetaData; +IF OBJECT_ID('tSQLt.NewConnection') IS NOT NULL DROP PROCEDURE tSQLt.NewConnection; +IF OBJECT_ID('tSQLt.CaptureOutput') IS NOT NULL DROP PROCEDURE tSQLt.CaptureOutput; +IF OBJECT_ID('tSQLt.SuppressOutput') IS NOT NULL DROP PROCEDURE tSQLt.SuppressOutput; +IF OBJECT_ID('tSQLt.Private_GetAnnotationList') IS NOT NULL DROP FUNCTION tSQLt.Private_GetAnnotationList; +IF TYPE_ID('tSQLt.[Private]') IS NOT NULL DROP TYPE tSQLt.[Private]; + +GO + GO ---Build+ GO diff --git a/TestUtil/tSQLtTestUtilCLR_CreateItems.sql b/TestUtil/tSQLtTestUtilCLR_CreateItems.sql index 300b8dcea..143ec61ef 100644 --- a/TestUtil/tSQLtTestUtilCLR_CreateItems.sql +++ b/TestUtil/tSQLtTestUtilCLR_CreateItems.sql @@ -24,3 +24,6 @@ GO CREATE FUNCTION tSQLt_testutil.AnEmptyClrTvf(@p1 NVARCHAR(MAX), @p2 NVARCHAR(MAX))RETURNS TABLE(id INT, val NVARCHAR(MAX)) AS EXTERNAL NAME tSQLtTestUtilCLR.[tSQLtTestUtilCLR.ClrFunctions].AnEmptyClrTvf; GO +CREATE PROCEDURE tSQLt_testutil.AClrSsp + AS EXTERNAL NAME tSQLtTestUtilCLR.[tSQLtTestUtilCLR.ClrStoredProcedures].AClrSsp; +GO diff --git a/Tests.SA/Tests.SA.ssmssqlproj b/Tests.SA/Tests.SA.ssmssqlproj index 2dfb28eb7..ac9b70c20 100644 --- a/Tests.SA/Tests.SA.ssmssqlproj +++ b/Tests.SA/Tests.SA.ssmssqlproj @@ -6,6 +6,12 @@ + + + + + _ExploratoryTests_SA.class.sql + diff --git a/Tests.SA/_ExploratoryTests_SA.class.sql b/Tests.SA/_ExploratoryTests_SA.class.sql new file mode 100644 index 000000000..cd352483e --- /dev/null +++ b/Tests.SA/_ExploratoryTests_SA.class.sql @@ -0,0 +1,31 @@ +EXEC tSQLt.NewTestClass '_ExploratoryTests_SA'; +GO +CREATE PROCEDURE [_ExploratoryTests_SA].[test MSSQL preserves COLLATION when using SELECT INTO across databases] +AS +BEGIN + SELECT + 'Hello World!' COLLATE SQL_Polish_CP1250_CI_AS c1, + 'Hello World!' COLLATE SQL_Latin1_General_CP437_BIN c2, + 'Hello World!' COLLATE Albanian_BIN2 c3 + INTO [_ExploratoryTests_SA].Table1 + + SELECT * INTO tempdb.dbo.Table2 FROM [_ExploratoryTests_SA].Table1 + + SELECT + C.name COLLATE DATABASE_DEFAULT name, + C.collation_name COLLATE DATABASE_DEFAULT collation_name + INTO #Expected + FROM sys.columns C + WHERE C.object_id = OBJECT_ID('[_ExploratoryTests_SA].Table1'); + + SELECT + C.name COLLATE DATABASE_DEFAULT name, + C.collation_name COLLATE DATABASE_DEFAULT collation_name + INTO #Actual + FROM tempdb.sys.columns C + WHERE C.object_id = OBJECT_ID('tempdb.dbo.Table2'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO diff --git a/Tests/AnnotationHostPlatformTests.class.sql b/Tests/AnnotationHostPlatformTests.class.sql index e25029ef1..6a5e4d5bf 100644 --- a/Tests/AnnotationHostPlatformTests.class.sql +++ b/Tests/AnnotationHostPlatformTests.class.sql @@ -6,7 +6,7 @@ BEGIN EXEC tSQLt.NewTestClass 'MyInnerTests' EXEC(' --[@'+'tSQLt:RunOnlyOnHostPlatform](''SomePlatform'') -CREATE PROCEDURE MyInnerTests.[test should execute] AS RAISERROR(''test executed'',16,10); +CREATE PROCEDURE MyInnerTests.[test should execute] AS RAISERROR(''MM>M>M'',16,10); '); EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_HostPlatform'; @@ -14,7 +14,7 @@ CREATE PROCEDURE MyInnerTests.[test should execute] AS RAISERROR(''test executed EXEC tSQLt_testutil.AssertTestErrors @TestName = 'MyInnerTests.[test should execute]', - @ExpectedMessage = 'test executed%'; + @ExpectedMessage = '%MM>M>M%'; END; GO CREATE PROCEDURE AnnotationHostPlatformTests.[test doesn't allow test to execute if actual host platform is not equal to the provided one] diff --git a/Tests/AnnotationNoTransactionTests.class.sql b/Tests/AnnotationNoTransactionTests.class.sql new file mode 100644 index 000000000..798b7748b --- /dev/null +++ b/Tests/AnnotationNoTransactionTests.class.sql @@ -0,0 +1,1581 @@ +EXEC tSQLt.NewTestClass 'AnnotationNoTransactionTests'; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test runs test without transaction] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test should execute outside of transaction] AS INSERT INTO #TranCount VALUES(''I'',@@TRANCOUNT);; + '); + + SELECT 'B' Id, @@TRANCOUNT TranCount + INTO #TranCount; + + EXEC tSQLt.Run 'MyInnerTests.[test should execute outside of transaction]'; + + INSERT INTO #TranCount VALUES('A',@@TRANCOUNT); + + SELECT Id, TranCount-MIN(TranCount)OVER() AdjustedTrancount + INTO #Actual + FROM #TranCount; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('B',0),('I',0),('A',0); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test transaction name is NULL in TestResults table] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test should execute outside of transaction] AS RETURN; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test should execute outside of transaction]'; + SELECT TranName INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES(NULL); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test if not NoTransaction TranName is valued in TestResults table] +AS +BEGIN + DECLARE @ActualTranName NVARCHAR(MAX); + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE MyInnerTests.[test should execute inside of transaction] AS RETURN;'); + + EXEC tSQLt.Run 'MyInnerTests.[test should execute inside of transaction]'; + SET @ActualTranName = (SELECT TranName FROM tSQLt.TestResult); + + EXEC tSQLt.AssertLike @ExpectedPattern = 'tSQLtTran%', @Actual = @ActualTranName; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test succeeding test gets correct entry in TestResults table] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test should execute outside of transaction] AS RETURN; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test should execute outside of transaction]'; + SELECT Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES('Success'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test failing test gets correct entry in TestResults table] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test should execute outside of transaction] AS EXEC tSQLt.Fail ''Some Obscure Reason''; + '); + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test should execute outside of transaction]'; + SELECT Result, Msg INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES('Failure','Some Obscure Reason'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE FUNCTION AnnotationNoTransactionTests.[Return 42424242 prefix before ERROR_MESSAGE()]() +RETURNS TABLE +AS +RETURN + SELECT '42424242:'+ERROR_MESSAGE() FormattedError; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test recoverable erroring test gets correct entry in TestResults table] +AS +BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_GetFormattedErrorInfo', @FakeFunctionName = 'AnnotationNoTransactionTests.[Return 42424242 prefix before ERROR_MESSAGE()]'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test should execute outside of transaction] AS RAISERROR (''Some Obscure Recoverable Error'', 16, 10); + '); + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test should execute outside of transaction]'; + SELECT Result, Msg INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES('Error','42424242:Some Obscure Recoverable Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt.Private_CleanUp is executed] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test1] AS RETURN; + '); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp'; + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT FullTestName INTO #Actual FROM tSQLt.Private_CleanUp_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + VALUES('[MyInnerTests].[test1]'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt.Private_CleanUp is not called if test is not annotated and passing] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE MyInnerTests.[test1] AS RETURN;'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp'; + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT * INTO #Actual FROM tSQLt.Private_CleanUp_SpyProcedureLog; + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt.Private_CleanUp is not called if test is not annotated and failing] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE MyInnerTests.[test1] AS EXEC tSQLt.Fail;'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp'; + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT * INTO #Actual FROM tSQLt.Private_CleanUp_SpyProcedureLog; + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt.Private_CleanUp is not called if test is not annotated and erroring] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE MyInnerTests.[test1] AS RAISERROR(''X'',16,10);'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp'; + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT * INTO #Actual FROM tSQLt.Private_CleanUp_SpyProcedureLog; + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt.Private_CleanUp error message is appended to tSQLt.TestResult.Msg] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test1] AS PRINT 1/0; + '); + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp', @CommandToExecute = 'SET @ErrorMsg = '''';'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + DECLARE @Actual NVARCHAR(MAX) = (SELECT Msg FROM tSQLt.TestResult); + + EXEC tSQLt.AssertLike @ExpectedPattern = '% ', @Actual = @Actual; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt.Private_CleanUp is called before the test result message is printed] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test1] AS RAISERROR('''',16,10); + '); + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp', @CommandToExecute = 'SET @ErrorMsg = '''';'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_Print'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + DECLARE @Actual NVARCHAR(MAX) = (SELECT Message FROM tSQLt.Private_Print_SpyProcedureLog WHERE Message LIKE '%%'); + + EXEC tSQLt.AssertLike @ExpectedPattern = '% ', @Actual = @Actual; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt tables are backed up before test is executed] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + INSERT INTO #Actual SELECT Action FROM tSQLt.Private_NoTransactionHandleTables_SpyProcedureLog; + END; + '); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp'; + SELECT Action INTO #Actual FROM tSQLt.Private_NoTransactionHandleTables_SpyProcedureLog; + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('Save'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test using SkipTest and NoTransaction annotation skips the test] +AS +BEGIN + CREATE TABLE #SkippedTestExecutionLog (Id INT); + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + --[@'+'tSQLt:SkipTest]('') + CREATE PROCEDURE MyInnerTests.[skippedTest] + AS + BEGIN + INSERT INTO #SkippedTestExecutionLog VALUES (1); + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[skippedTest]'; + + EXEC tSQLt.AssertEmptyTable @TableName = '#SkippedTestExecutionLog'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test does not call 'Save' if @NoTransactionFlag=0;] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + INSERT INTO #Actual SELECT Action FROM tSQLt.Private_NoTransactionHandleTables_SpyProcedureLog; + END; + '); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + SELECT TOP(0) Action INTO #Actual FROM tSQLt.Private_NoTransactionHandleTables_SpyProcedureLog; + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test does not call tSQLt.Private_NoTransactionHandleTables if @NoTransactionFlag=1 and @SkipTestFlag=1] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + --[@'+'tSQLt:SkipTest]('''') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + EXEC tSQLt.AssertEmptyTable @TableName = 'tSQLt.Private_NoTransactionHandleTables_SpyProcedureLog'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[Redact IsTestObject status on all objects] +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX); + WITH MarkedTestDoubles AS + ( + SELECT + TempO.Name, + SCHEMA_NAME(TempO.schema_id) SchemaName, + TempO.type ObjectType + FROM sys.tables TempO + JOIN sys.extended_properties AS EP + ON EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = TempO.object_id + AND EP.name = 'tSQLt.IsTempObject' + AND EP.value = 1 + ) + SELECT @cmd = + ( + SELECT + 'EXEC sp_updateextendedproperty ''tSQLt.IsTempObject'',-1342,''SCHEMA'', '''+MTD.SchemaName+''', ''TABLE'', '''+MTD.Name+''';' + FROM MarkedTestDoubles MTD + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + EXEC(@cmd); +END; +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[Restore IsTestObject status on all objects] +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX); + WITH MarkedTestDoubles AS + ( + SELECT + TempO.Name, + SCHEMA_NAME(TempO.schema_id) SchemaName, + TempO.type ObjectType + FROM sys.tables TempO + JOIN sys.extended_properties AS EP + ON EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = TempO.object_id + AND EP.name = 'tSQLt.IsTempObject' + AND EP.value = -1342 + ) + SELECT @cmd = + ( + SELECT + 'EXEC sp_updateextendedproperty ''tSQLt.IsTempObject'',1,''SCHEMA'', '''+MTD.SchemaName+''', ''TABLE'', '''+MTD.Name+''';' + FROM MarkedTestDoubles MTD + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + EXEC(@cmd); +END; +GO +CREATE FUNCTION AnnotationNoTransactionTests.PassThrough(@TestName NVARCHAR(MAX)) +RETURNS TABLE +AS +RETURN + SELECT @TestName TestName +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[CLEANUP: test an unrecoverable erroring test gets correct (Success/Failure but not Error) entry in TestResults table] +AS +BEGIN + EXEC tSQLt.SetSummaryError 1; + EXEC tSQLt.DropClass MyInnerTests; + --EXEC tSQLt.UndoTestDoubles; + --ROLLBACK +END; +GO + +--[@tSQLt:NoTransaction]('AnnotationNoTransactionTests.[CLEANUP: test an unrecoverable erroring test gets correct (Success/Failure but not Error) entry in TestResults table]') +/* This test must be NoTransaction because the inner test will invalidate any open transaction causing chaos and turmoil in the reactor. */ +CREATE PROCEDURE AnnotationNoTransactionTests.[test an unrecoverable erroring test gets correct entry in TestResults table] +AS +BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_GetLastTestNameIfNotProvided', @FakeFunctionName = 'AnnotationNoTransactionTests.PassThrough'; /* --<-- Prevent tSQLt-internal turmoil */ + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_SaveTestNameForSession';/* --<-- Prevent tSQLt-internal turmoil */ + EXEC ('CREATE SCHEMA MyInnerTests AUTHORIZATION [tSQLt.TestClass];'); + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test should cause unrecoverable error] AS PRINT CAST(''Some obscure string'' AS INT); + '); + + /*******************************************************************************************************************************/ + /************************* MESSING WITH THIS CODE WILL PUT tSQLt INTO AN INVALID STATE! ****************************************/ + /**/CREATE TABLE #CleanUpProcedures_StopExecutionForInnerTests(I INT); + /**/EXEC tSQLt.SpyProcedure + /**/ @ProcedureName = 'tSQLt.Private_CleanUp', + /**/ @CommandToExecute = 'IF(OBJECT_ID(''tempdb..#CleanUpProcedures_StopExecutionForInnerTests'')IS NOT NULL)BEGIN RETURN;END;', + /**/ @CallOriginal = 1; + /**/EXEC tSQLt.SpyProcedure + /**/ @ProcedureName = 'tSQLt.Private_AssertNoSideEffects', + /**/ @CommandToExecute = 'IF(OBJECT_ID(''tempdb..#CleanUpProcedures_StopExecutionForInnerTests'')IS NOT NULL)BEGIN RETURN;END;', + /**/ @CallOriginal = 1; + /************************* MESSING WITH THIS CODE WILL PUT tSQLt INTO AN INVALID STATE! ****************************************/ + /*******************************************************************************************************************************/ + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test should cause unrecoverable error]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + DECLARE @Actual NVARCHAR(MAX) = (SELECT ISNULL(Result,'')+'<><><>'+ISNULL(Msg,'') FROM tSQLt.TestResult WHERE Name = '[MyInnerTests].[test should cause unrecoverable error]'); + + EXEC tSQLt.AssertLike @ExpectedPattern = 'Error<><><>%Conversion failed when converting the varchar value ''Some obscure string'' to data type int.%', @Actual = @Actual; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp is executed after test completes] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp1] + AS + BEGIN + INSERT INTO #Actual VALUES (''UserCleanUp1''); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp1]'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + INSERT INTO #Actual VALUES (''test1''); + END; + '); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1, 'test1'), (2, 'UserCleanUp1'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp is executed even if it has a single quote in its name and/or its schema name] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInner''Tests' + EXEC(' + CREATE PROCEDURE [MyInner''Tests].[UserClean''Up1] + AS + BEGIN + INSERT INTO #Actual VALUES (''UserClean''''Up1''); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInner''''Tests].[UserClean''''Up1]'') + CREATE PROCEDURE [MyInner''Tests].[test''1] + AS + BEGIN + RETURN + END; + '); + + CREATE TABLE #Actual (col1 NVARCHAR(MAX)); + + + EXEC tSQLt.Run '[MyInner''Tests].[test''1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('UserClean''Up1'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test annotation throws appropriate error if specified Test-CleanUp does not exist] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUpDoesNotExist]'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN + END; + '); + + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, + TR.Result, + TR.Msg + INTO #Actual + FROM tSQLt.TestResult AS TR + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES ( + '[MyInnerTests].[test1]', + 'Error', + 'There is a problem with this annotation: [@tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUpDoesNotExist]'') +Original Error: {16,10;(null)} Test CleanUp Procedure [MyInnerTests].[UserCleanUpDoesNotExist] does not exist or is not a procedure.') + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test annotation throws appropriate error if specified Test-CleanUp is not a procedure] + +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE VIEW [MyInnerTests].[NotAProcedure] AS SELECT 1 X;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[NotAProcedure]'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + DECLARE @Actual NVARCHAR(MAX) = (SELECT Msg FROM tSQLt.TestResult AS TR); + EXEC tSQLt.AssertLike @ExpectedPattern = '%Test CleanUp Procedure [[]MyInnerTests].[[]NotAProcedure] does not exist or is not a procedure.', @Actual = @Actual; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test annotation does not throw error if specified Test-CleanUp is a CLR stored procedure] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](''tSQLt_testutil.AClrSsp'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN + END; + '); + EXEC tSQLt.SetSummaryError @SummaryError = 1; + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.Run 'MyInnerTests.[test1]'--, @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt executes multiple Test-CleanUp in the order they are specified] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUp7] AS BEGIN INSERT INTO #Actual VALUES (''CleanUp7''); END;'); + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUp3] AS BEGIN INSERT INTO #Actual VALUES (''CleanUp3''); END;'); + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUp9] AS BEGIN INSERT INTO #Actual VALUES (''CleanUp9''); END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](''MyInnerTests.CleanUp7'') + --[@'+'tSQLt:NoTransaction](''MyInnerTests.CleanUp3'') + --[@'+'tSQLt:NoTransaction](''MyInnerTests.CleanUp9'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + INSERT INTO #Actual VALUES (''test1''); + END; + '); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1, 'test1'), (2, 'CleanUp7'),(3, 'CleanUp3'), (4, 'CleanUp9'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp is executed after Test-CleanUp] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE [MyInnerTests].[TestCleanUp] AS BEGIN INSERT INTO #Actual VALUES (''TestCleanUp''); END;'); + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUp] AS BEGIN INSERT INTO #Actual VALUES (''(Schema)CleanUp''); END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](''MyInnerTests.TestCleanUp'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + INSERT INTO #Actual VALUES (''test1''); + END; + '); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1, 'test1'), (2, 'TestCleanUp'),(3, '(Schema)CleanUp'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp is executed only if it is a stored procedure] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE VIEW [MyInnerTests].[CleanUp] AS SELECT 1 X;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp is executed if schema name contains single quote] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInner''Tests' + EXEC('CREATE PROCEDURE [MyInner''Tests].[CleanUp] AS BEGIN INSERT INTO #Actual VALUES (''[MyInner''''Tests].[CleanUp]''); END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInner''Tests].[test1] + AS + BEGIN + RETURN; + END; + '); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + + EXEC tSQLt.Run '[MyInner''Tests].[test1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1, '[MyInner''Tests].[CleanUp]'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp is executed even if name is differently cased] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE [MyInnerTests].[clEaNuP] AS BEGIN INSERT INTO #Actual VALUES (''[MyInnerTests].[clEaNuP]''); END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + + EXEC tSQLt.Run '[MyInnerTests].[test1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1, '[MyInnerTests].[clEaNuP]'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp error causes test result to be Error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[CleanUp] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('[MyInnerTests].[test1]','Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE FUNCTION AnnotationNoTransactionTests.[return 42134213 if correct error]() +RETURNS TABLE +AS +RETURN + SELECT '42134213' FormattedError WHERE ERROR_MESSAGE() = 'This is an error ;)'; +GO +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE FUNCTION AnnotationNoTransactionTests.[return 42424242+@NewMessage, @NewResult]( + @PrevMessage NVARCHAR(MAX), + @PrevResult NVARCHAR(MAX), + @NewMessage NVARCHAR(MAX), + @NewResult NVARCHAR(MAX) +) +RETURNS TABLE +AS +RETURN + SELECT '42424242:'+@NewMessage Message, @NewResult Result +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp error causes an appropriate message to be written to the tSQLt.TestResult table] +AS +BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_GetFormattedErrorInfo', @FakeFunctionName = 'AnnotationNoTransactionTests.[return 42134213 if correct error]'; + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_HandleMessageAndResult', @FakeFunctionName = 'AnnotationNoTransactionTests.[return 42424242+@NewMessage, @NewResult]'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[CleanUp] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Msg, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES('42424242:Error during clean up: (42134213)','Error'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO + +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test appends message to any test error if Schema-CleanUp errors] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[CleanUp] + AS + BEGIN + RAISERROR(''This is a CleanUp error ;)'',15,12); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RAISERROR(''This is a Test error ;)'',16,10); + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + DECLARE @FriendlyMsg NVARCHAR(MAX) = (SELECT TR.Msg FROM tSQLt.TestResult AS TR); + + EXEC tSQLt.AssertLike @ExpectedPattern = '%This is a Test error ;)% || %This is a CleanUp error ;)%', @Actual = @FriendlyMsg; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp is executed even if the test errors] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp1] + AS + BEGIN + INSERT INTO #Actual VALUES (''UserCleanUp1''); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp1]'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + INSERT INTO #Actual VALUES (''test1''); + END; + '); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + + + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1, 'test1'), (2, 'UserCleanUp1'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp error causes failing test to be set to Error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[CleanUp] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + EXEC tSQLt.Fail; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('[MyInnerTests].[test1]','Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp error causes passing test to be set to Error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[CleanUp] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('[MyInnerTests].[test1]','Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp error and test error still results in Error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[CleanUp] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RAISERROR(''Some random error!'', 16, 10); + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('[MyInnerTests].[test1]','Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test appends message to any test error if Test-CleanUp errors] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[TestCleanUp] + AS + BEGIN + RAISERROR(''This is a CleanUp error ;)'',15,12); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[TestCleanUp]'') + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RAISERROR(''This is a Test error ;)'',16,10); + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + DECLARE @FriendlyMsg NVARCHAR(MAX) = (SELECT TR.Msg FROM tSQLt.TestResult AS TR); + + EXEC tSQLt.AssertLike @ExpectedPattern = '%This is a Test error ;)% || %This is a CleanUp error ;)%', @Actual = @FriendlyMsg; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp is executed if previous Test-CleanUp errors] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp1] + AS + BEGIN + INSERT INTO #Actual VALUES (''UserCleanUp1''); + END; + '); + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp2] + AS + BEGIN + INSERT INTO #Actual VALUES (''UserCleanUp2''); + RAISERROR(''some error in UserCleanUp2'',16,10); + END; + '); + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp3] + AS + BEGIN + INSERT INTO #Actual VALUES (''UserCleanUp3''); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp1]'') + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp2]'') + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp3]'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + INSERT INTO #Actual VALUES (''test1''); + END; + '); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + + + EXEC tSQLt.Run 'MyInnerTests.[test1]', 'tSQLt.NullTestResultFormatter'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1, 'test1'), (2, 'UserCleanUp1'), (3, 'UserCleanUp2'), (4, 'UserCleanUp3'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp appends all individual error messages] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[CleanUp] + AS + BEGIN + RAISERROR(''some error in Schema-CleanUp'',16,10); + END; + '); + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp1] + AS + BEGIN + RAISERROR(''some error in UserCleanUp1'',16,10); + END; + '); + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp2] + AS + BEGIN + RAISERROR(''some error in UserCleanUp2'',16,10); + END; + '); + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp3] + AS + BEGIN + RAISERROR(''some error in UserCleanUp3'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp1]'') + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp2]'') + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp3]'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + EXEC tSQLt.Fail ''MyInnerTests.test1 has failed.''; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', 'tSQLt.NullTestResultFormatter'; + DECLARE @Actual NVARCHAR(MAX) = (SELECT Msg FROM tSQLt.TestResult); + + EXEC tSQLt.AssertLike @ExpectedPattern = '%MyInnerTests.test1%UserCleanUp1%UserCleanUp2%UserCleanUp3%Schema-CleanUp%', @Actual = @Actual; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp error causes failing test to be set to Error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[TestCleanUp1] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[TestCleanUp1]'') + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + EXEC tSQLt.Fail; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('[MyInnerTests].[test1]','Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp error causes passing test to be set to Error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[TestCleanUp1] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[TestCleanUp1]'') + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('[MyInnerTests].[test1]','Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp error and test error still results in Error] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[TestCleanUp1] + AS + BEGIN + RAISERROR(''This is an error ;)'',16,10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[TestCleanUp1]'') + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RAISERROR(''test error'',16,10); + END; + '); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TR.Name, TR.Result INTO #Actual FROM tSQLt.TestResult AS TR; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('[MyInnerTests].[test1]','Error'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp error causes an appropriate message to be written to tSQLt.TestResult even if ERROR_PROCEDURE is null] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyOtherInnerTests' + EXEC(' + CREATE PROCEDURE [MyOtherInnerTests].[TestCleanUp1] + AS + BEGIN + /*wasting lines...*/ + EXEC(''RAISERROR(''''This is another error ;)'''',15,12)''); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyOtherInnerTests].[TestCleanUp1]'') + CREATE PROCEDURE [MyOtherInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.Run 'MyOtherInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + DECLARE @FriendlyMsg NVARCHAR(MAX) = (SELECT TR.Msg FROM tSQLt.TestResult AS TR); + EXEC tSQLt.AssertLike @ExpectedPattern = 'Error during clean up: (%This is another error ;)%Procedure: %)', @Actual = @FriendlyMsg; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Private-CleanUp error stops execution of all subsequent tests] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test1] AS INSERT INTO #Actual DEFAULT VALUES; + '); + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test2] AS INSERT INTO #Actual DEFAULT VALUES; + '); + CREATE TABLE #Actual(Id CHAR(1) DEFAULT '*'); + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp', @CommandToExecute = 'SET @Result = ''Abort''; SET @ErrorMsg = ''Error in Private_CleanUp'';'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + BEGIN TRY + EXEC tSQLt.Run 'MyInnerTests'; + END TRY + BEGIN CATCH + /*Not interested in the specific error here.*/ + END CATCH; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('*'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Private_CleanUp @Result OUTPUT gets written to tSQLt.TestResult before tSQLt stops] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTests.[test1] AS RETURN; + '); + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp', @CommandToExecute = 'SET @Result = ''V1234'';'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + + EXEC tSQLt.Run 'MyInnerTests'; + + SELECT Result INTO #Actual FROM tSQLt.TestResult + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('V1234'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp is executed through tSQLt.Private_CleanUpCmdHandler] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUp] AS BEGIN RETURN; END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUpCmdHandler'; + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT _id_, CleanUpCmd INTO #Actual FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog + WHERE(NOT EXISTS(SELECT 1 FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog WHERE CleanUpCmd LIKE '%MyInnerTests%CleanUp%')); + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual', @Message = 'Expected a call for MyInnerTests.Cleanup1'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp is executed through tSQLt.Private_CleanUpCmdHandler] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE [MyInnerTests].[Test-CleanUp1] AS BEGIN RETURN; END;'); + EXEC('CREATE PROCEDURE [MyInnerTests].[Test-CleanUp2] AS BEGIN RETURN; END;'); + EXEC('CREATE PROCEDURE [MyInnerTests].[Test-CleanUp3] AS BEGIN RETURN; END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[Test-CleanUp1]'') + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[Test-CleanUp2]'') + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[Test-CleanUp3]'') + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUpCmdHandler'; + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT _id_, CleanUpCmd INTO #Actual FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog + WHERE(NOT EXISTS(SELECT 1 FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog WHERE CleanUpCmd LIKE '%MyInnerTests%Test-CleanUp1%')) + OR(NOT EXISTS(SELECT 1 FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog WHERE CleanUpCmd LIKE '%MyInnerTests%Test-CleanUp2%')) + OR(NOT EXISTS(SELECT 1 FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog WHERE CleanUpCmd LIKE '%MyInnerTests%Test-CleanUp3%')); + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual', @Message = 'Expected a call for MyInnerTests.Test-Cleanup(s)'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Schema-CleanUp is executed in the order specified and again at the end] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUpA] AS BEGIN INSERT INTO #Actual VALUES(OBJECT_NAME(@@PROCID)); END;'); + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUpB] AS BEGIN INSERT INTO #Actual VALUES(OBJECT_NAME(@@PROCID)); END;'); + EXEC('CREATE PROCEDURE [MyInnerTests].[CleanUp] AS BEGIN INSERT INTO #Actual VALUES(OBJECT_NAME(@@PROCID)); END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](''MyInnerTests.CleanUpA'') + --[@'+'tSQLt:NoTransaction](''MyInnerTests.CleanUp'') + --[@'+'tSQLt:NoTransaction](''MyInnerTests.CleanUpB'') + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + CREATE TABLE #Actual(OrderNumber INT IDENTITY(1,1), CleanUpName NVARCHAR(MAX) ); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES(1,'CleanUpA'),(2,'CleanUp'),(3,'CleanUpB'),(4,'CleanUp'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Test-CleanUp is executed multiple times if it is specified multiple times] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC('CREATE PROCEDURE [MyInnerTests].[Test-CleanUp1] AS BEGIN INSERT INTO #Actual DEFAULT VALUES; END;'); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[Test-CleanUp1]'') + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[Test-CleanUp1]'') + --[@'+'tSQLt:NoTransaction](''MyInnerTests.[Test-CleanUp1]'') + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + + CREATE TABLE #Actual(WasCalled BIT DEFAULT 1); + + EXEC tSQLt.Run 'MyInnerTests.[test1]', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES(1),(1),(1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test any CleanUp (Test, Schema, or Private) that alters the test result adds the previous result to the error message] +AS +BEGIN + DECLARE @TestMessage NVARCHAR(MAX) = 'BeforeMessage'; + + EXEC tSQLt.Private_CleanUpCmdHandler + @CleanUpCmd='RAISERROR(''NewMessage'',16,10)', + @TestMsg = @TestMessage OUT, + @TestResult = 'BeforeResult' , + @ResultInCaseOfError = 'NewResult'; + + EXEC tSQLt.AssertLike @ExpectedPattern = 'BeforeMessage [[]Result: BeforeResult] || %NewMessage%', @Actual = @TestMessage; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test any CleanUp adds the previous result to the error message even if the previous result is NULL] +AS +BEGIN + DECLARE @TestMessage NVARCHAR(MAX) = 'BeforeMessage'; + + EXEC tSQLt.Private_CleanUpCmdHandler + @CleanUpCmd='RAISERROR(''NewMessage'',16,10)', + @TestMsg = @TestMessage OUT, + @TestResult = NULL , + @ResultInCaseOfError = 'NewResult'; + + EXEC tSQLt.AssertLike @ExpectedPattern = 'BeforeMessage [[]Result: ] || %NewMessage%', @Actual = @TestMessage; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Cleanup is executed after the outer-most test execution try catch and before writing to TestResult] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables', @CommandToExecute = 'IF(@Action = ''Save'')RAISERROR(''Error caught in outermost try-catch'', 16, 10);'; + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + CREATE PROCEDURE [MyInnerTests].[UserCleanUp1] + AS + BEGIN + RAISERROR(''Error in UserCleanUp1'', 16, 10); + END; + '); + EXEC(' + --[@'+'tSQLt:NoTransaction](''[MyInnerTests].[UserCleanUp1]'') + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.SetSummaryError @SummaryError = 0; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + DECLARE @Actual NVARCHAR(MAX) = (SELECT Msg FROM tSQLt.TestResult); + + EXEC tSQLt.AssertLike @ExpectedPattern = '%Error caught in outermost try-catch%Error in UserCleanUp1%', @Actual = @Actual; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Private_AssertNoSideEffects is executed after all CleanUps and throws an error if there are new/missing/renamed objects] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC ('CREATE TABLE MyInnerTests.TestTable1 (Id INT);'); + EXEC ('CREATE TABLE MyInnerTests.TestTable2 (Id INT);'); + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN; + END; + '); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUp', @CommandToExecute = 'DROP TABLE MyInnerTests.TestTable1; CREATE TABLE MyInnerTests.TestTable1 (Id INT); EXEC sys.sp_rename ''MyInnerTests.TestTable2'', ''TestTable3'';'; + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + + DECLARE @Actual NVARCHAR(MAX) = (SELECT Msg FROM tSQLt.TestResult); + + EXEC tSQLt.AssertLike @ExpectedPattern = '%Added%TestTable1%Added%TestTable3%Deleted%TestTable1%Deleted%TestTable2%', @Actual = @Actual; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test Private-CleanUp when @Result = FATAL, it is not overwritten by another Result] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN; + END; + '); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables', @CommandToExecute = 'IF(@Action=''Reset'')RAISERROR(''AFatalError'',16,10);'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles', @CommandToExecute = 'RAISERROR(''AnAbortError'',16,10);'; + + EXEC tSQLt.SetSummaryError 0; + BEGIN TRY + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + END TRY + BEGIN CATCH + /*-- not really interested in this error --*/ + END CATCH; + + DECLARE @Actual NVARCHAR(MAX) = (SELECT Result FROM tSQLt.TestResult); + + EXEC tSQLt.AssertEqualsString @Expected = 'FATAL', @Actual = @Actual; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE AnnotationNoTransactionTests.[test tSQLt.Private_RunTest_TestExecution is not called if SkipTest & NoTransaction] +AS +BEGIN + --EXEC tSQLt.Fail 'This test has gone horribly wrong and kills tSQLt.'; + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + --[@'+'tSQLt:SkipTest](DEFAULT) + CREATE PROCEDURE MyInnerTests.[test1] + AS + BEGIN + RETURN; + END; + '); + + DECLARE @Actual TABLE(_id_ INT, TestName NVARCHAR(MAX));/*-- @TableVariables persist regardless of transaction rollbacks --*/ + DECLARE @TranName CHAR(32);EXEC tSQLt.GetNewTranName @TranName=@TranName OUT; + SAVE TRAN @TranName; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_RunTest_TestExecution', @CallOriginal=1; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; /*--<-- only needed if test fails, so we get a clearer fail message --<--*/ + + EXEC tSQLt.SetSummaryError 0; + EXEC tSQLt.Run 'MyInnerTests.[test1]'; + INSERT INTO @Actual SELECT _id_,TestName FROM tSQLt.Private_RunTest_TestExecution_SpyProcedureLog; + ROLLBACK TRAN @TranName; /*-- Rolling back just the last 5 lines (though, not the insert into @Actual). --*/ + + SELECT * INTO #Actual FROM @Actual; + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO diff --git a/Tests/AnnotationSqlServerVersionTests.class.sql b/Tests/AnnotationSqlServerVersionTests.class.sql index 7290296c3..6faa12a79 100644 --- a/Tests/AnnotationSqlServerVersionTests.class.sql +++ b/Tests/AnnotationSqlServerVersionTests.class.sql @@ -16,7 +16,7 @@ BEGIN EXEC tSQLt.NewTestClass 'MyInnerTests' EXEC(' --[@'+'tSQLt:MinSqlMajorVersion](13) -CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''<><><> test executed <><><>'',16,10); '); EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[42.17.1986.57]'; @@ -24,7 +24,7 @@ CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test exec EXEC tSQLt_testutil.AssertTestErrors @TestName = 'MyInnerTests.[test should not execute]', - @ExpectedMessage = 'test executed%'; + @ExpectedMessage = '%<><><> test executed <><><>%'; END; GO CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MinSqlMajorVersion doesn't allow test to execute if actual version is smaller] @@ -65,7 +65,7 @@ BEGIN EXEC tSQLt.NewTestClass 'MyInnerTests' EXEC(' --[@'+'tSQLt:MinSqlMajorVersion](42) -CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''<><><> test executed <><><>'',16,10); '); EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[42.17.1986.57]'; @@ -73,7 +73,7 @@ CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test exec EXEC tSQLt_testutil.AssertTestErrors @TestName = 'MyInnerTests.[test should not execute]', - @ExpectedMessage = 'test executed%'; + @ExpectedMessage = '%<><><> test executed <><><>%'; END; GO CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MinSqlMajorVersion provides a useful skip reason] @@ -116,7 +116,7 @@ BEGIN EXEC tSQLt.NewTestClass 'MyInnerTests' EXEC(' --[@'+'tSQLt:MaxSqlMajorVersion](43) -CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''<><><> test executed! <><><>'',16,10); '); EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[42.17.1986.57]'; @@ -124,7 +124,7 @@ CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test exec EXEC tSQLt_testutil.AssertTestErrors @TestName = 'MyInnerTests.[test should not execute]', - @ExpectedMessage = 'test executed%'; + @ExpectedMessage = '%<><><> test executed! <><><>%'; END; GO CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MaxSqlMajorVersion doesn't allow test to execute if actual version is larger] @@ -165,7 +165,7 @@ BEGIN EXEC tSQLt.NewTestClass 'MyInnerTests' EXEC(' --[@'+'tSQLt:MaxSqlMajorVersion](42) -CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test executed'',16,10); +CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''<><><> test executed <><><>'',16,10); '); EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_SqlVersion', @FakeFunctionName = 'AnnotationSqlServerVersionTests.[42.17.1986.57]'; @@ -173,7 +173,7 @@ CREATE PROCEDURE MyInnerTests.[test should not execute] AS RAISERROR(''test exec EXEC tSQLt_testutil.AssertTestErrors @TestName = 'MyInnerTests.[test should not execute]', - @ExpectedMessage = 'test executed%'; + @ExpectedMessage = '%<><><> test executed <><><>%'; END; GO CREATE PROCEDURE AnnotationSqlServerVersionTests.[test MaxSqlMajorVersion provides a useful skip reason] diff --git a/Tests/AssertEqualsTableSchemaTests.class.sql b/Tests/AssertEqualsTableSchemaTests.class.sql index 71d79aac5..4215285c3 100644 --- a/Tests/AssertEqualsTableSchemaTests.class.sql +++ b/Tests/AssertEqualsTableSchemaTests.class.sql @@ -190,7 +190,7 @@ BEGIN EXEC tSQLt_testutil.assertFailCalled @Command, 'AssertEqualsTableSchema did not call Fail'; END; GO -CREATE PROCEDURE AssertEqualsTableSchemaTests.[test fail if 2nd table has Column with different colation order] +CREATE PROCEDURE AssertEqualsTableSchemaTests.[test fail if 2nd table has Column with different collation order] AS BEGIN CREATE TABLE AssertEqualsTableSchemaTests.Tbl1( @@ -288,6 +288,23 @@ BEGIN EXEC tSQLt.AssertEqualsTableSchema @Expected = 'AssertEqualsTableSchemaTests.Tbl1', @Actual = 'AssertEqualsTableSchemaTests.Tbl2'; END; GO +--[@tSQLt:SkipTest]('Not currently supported. See Issue https://github.com/tSQLt-org/tSQLt/issues/119') +CREATE PROCEDURE AssertEqualsTableSchemaTests.[test calls fail if tables are #temporary and their schema does not match] +AS +BEGIN + CREATE TABLE #Actual( + Id INT PRIMARY KEY, + NoKey INT NULL + ); + CREATE TABLE #Expected( + Id BIT PRIMARY KEY, + NoKey NVARCHAR(MAX) NULL + ); + DECLARE @Command VARCHAR(MAX) = 'EXEC tSQLt.AssertEqualsTableSchema @Expected = ''#Expected'', @Actual = ''#Actual'';'; + EXEC tSQLt_testutil.assertFailCalled @Command, 'AssertEqualsTableSchema did not call Fail'; + +END; +GO /* SELECT diff --git a/Tests/AssertEqualsTableTests.class.sql b/Tests/AssertEqualsTableTests.class.sql index daff47a8f..08fb5b046 100644 --- a/Tests/AssertEqualsTableTests.class.sql +++ b/Tests/AssertEqualsTableTests.class.sql @@ -468,11 +468,10 @@ GO CREATE PROCEDURE AssertEqualsTableTests.[test considers NULL values identical] AS BEGIN - SELECT * - INTO AssertEqualsTableTests.NullCellTableCopy - FROM tSQLt.Private_NullCellTable; + SELECT NULL [aNULLColumn] INTO #Actual; + SELECT NULL [aNULLColumn] INTO #Expected; - EXEC tSQLt.AssertEqualsTable 'tSQLt.Private_NullCellTable', 'AssertEqualsTableTests.NullCellTableCopy'; + EXEC tSQLt.AssertEqualsTable #Expected, #Actual; END; GO @@ -705,7 +704,6 @@ BEGIN EXEC tSQLt.AssertEqualsTable 'expected', 'actual'; END; GO - CREATE PROC AssertEqualsTableTests.test_AssertEqualsTable_works_with_expected_having_identity_column AS BEGIN @@ -785,4 +783,75 @@ BEGIN EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can NOT handle a datatype] 'GEOMETRY', 'geometry::STPointFromText(''POINT (10 10)'', 0),geometry::STPointFromText(''POINT (11 11)'', 0),geometry::STPointFromText(''POINT (12 12)'', 0)'; EXEC AssertEqualsTableTests.[Assert that AssertEqualsTable can NOT handle a datatype] 'GEOGRAPHY', 'geography::STGeomFromText(''LINESTRING(-10.10 10.10, -50.10 50.10)'', 4326),geography::STGeomFromText(''LINESTRING(-11.11 11.11, -50.11 50.11)'', 4326),geography::STGeomFromText(''LINESTRING(-12.12 12.12, -50.12 50.12)'', 4326)'; END; -GO \ No newline at end of file +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC AssertEqualsTableTests.[test RC table is marked as tSQLt.IsTempObject] +AS +BEGIN + CREATE TABLE #Table1 (id INT); + CREATE TABLE #Table2 (id INT); + + SELECT name, + object_id, + schema_id + INTO #TableListBefore + FROM sys.tables + WHERE name LIKE 'tSQLt[_]tempobject[_]%'; + + EXEC tSQLt.AssertEqualsTable '#Table1','#Table2'; + + SELECT QUOTENAME(SCHEMA_NAME(NewTable.schema_id))+'.'+QUOTENAME(NewTable.name) TableName, EP.value AS [tSQLt.IsTempObject] + INTO #Actual + FROM( + SELECT name, object_id, schema_id FROM sys.tables WHERE name LIKE 'tSQLt[_]tempobject[_]%' + EXCEPT + SELECT name, object_id, schema_id FROM #TableListBefore AS TLB + ) NewTable + LEFT JOIN sys.extended_properties EP + ON EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.name = 'tSQLt.IsTempObject' + AND NewTable.object_id = EP.major_id + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected SELECT TableName, 1 FROM #Actual; + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC AssertEqualsTableTests.[test RC table is created in the tSQLt schema] +AS +BEGIN + CREATE TABLE #Table1 (id INT); + CREATE TABLE #Table2 (id INT); + + SELECT name, + object_id, + schema_id + INTO #TableListBefore + FROM sys.tables + WHERE name LIKE 'tSQLt[_]tempobject[_]%'; + + EXEC tSQLt.AssertEqualsTable '#Table1','#Table2'; + + SELECT SCHEMA_NAME(NewTable.schema_id) SchemaName, NewTable.name TableName + INTO #Actual + FROM( + SELECT name, object_id, schema_id FROM sys.tables WHERE name LIKE 'tSQLt[_]tempobject[_]%' + EXCEPT + SELECT name, object_id, schema_id FROM #TableListBefore AS TLB + ) NewTable + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected SELECT 'tSQLt', TableName FROM #Actual; + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + diff --git a/Tests/AssertResultSetsHaveSameMetaDataTests.class.sql b/Tests/AssertResultSetsHaveSameMetaDataTests.class.sql index e0f0177cd..e62472991 100644 --- a/Tests/AssertResultSetsHaveSameMetaDataTests.class.sql +++ b/Tests/AssertResultSetsHaveSameMetaDataTests.class.sql @@ -48,10 +48,10 @@ BEGIN END; GO -CREATE PROC tSQLt_test_AssertResultSetsHaveSameMetaData.[xtest AssertResultSetsHaveSameMetaData fails when one result set has no rows for versions before SQL Server 2012] +--[@tSQLt:SkipTest]('INTENTIONALLY DISABLED UNTIL WE FIGURE OUT WHY IT SOMETIMES FAILS AND SOMETIMES PASSES') +CREATE PROC tSQLt_test_AssertResultSetsHaveSameMetaData.[test AssertResultSetsHaveSameMetaData fails when one result set has no rows for versions before SQL Server 2012] AS BEGIN - -- INTENTIONALLY DISABLED UNTIL WE FIGURE OUT WHY IT SOMETIMES FAILS AND SOMETIMES PASSES IF (SELECT CAST(SUBSTRING(product_version, 1, CHARINDEX('.', product_version) - 1) AS INT) FROM sys.dm_os_loaded_modules WHERE name LIKE '%\sqlservr.exe') >= 11 BEGIN EXEC tSQLt.AssertResultSetsHaveSameMetaData diff --git a/Tests/BootStrapTest.sql b/Tests/BootStrapTest.sql index bb8975070..1f8953ec0 100644 --- a/Tests/BootStrapTest.sql +++ b/Tests/BootStrapTest.sql @@ -49,7 +49,7 @@ BEGIN CATCH END CATCH; BEGIN TRY - IF NOT EXISTS(SELECT 1 FROM tSQLt.TestResult WHERE Msg LIKE 'TestCase was executed! (42)%') + IF NOT EXISTS(SELECT 1 FROM tSQLt.TestResult WHERE Msg LIKE '%TestCase was executed! (42)%') RAISERROR('TestCase was not executed',16,10); PRINT 'Test passed'; diff --git a/Tests/DropClassTests.class.sql b/Tests/DropClassTests.class.sql index a397a6c5e..4d4005976 100644 --- a/Tests/DropClassTests.class.sql +++ b/Tests/DropClassTests.class.sql @@ -249,5 +249,88 @@ BEGIN END; GO +CREATE PROC DropClassTests.[test removes tables referencing each other] +AS +BEGIN + + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE TABLE MyTestClass.TableA(I INT PRIMARY KEY);'); + EXEC('CREATE TABLE MyTestClass.TableB(I INT PRIMARY KEY REFERENCES MyTestClass.TableA(I));'); + EXEC('ALTER TABLE MyTestClass.TableA ADD FOREIGN KEY (I) REFERENCES MyTestClass.TableB(I);'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROC DropClassTests.[test removes tables referencing each other with names that require quoting ( .')] +AS +BEGIN + + EXEC('CREATE SCHEMA [My.Tes''t Class];'); + EXEC('CREATE TABLE [My.Tes''t Class].[Ta.b''le A](I INT PRIMARY KEY);'); + EXEC('CREATE TABLE [My.Tes''t Class].[Ta.b''le B](I INT PRIMARY KEY CONSTRAINT [FK: Ta.b''le B] REFERENCES [My.Tes''t Class].[Ta.b''le A](I));'); + EXEC('ALTER TABLE [My.Tes''t Class].[Ta.b''le A] ADD CONSTRAINT [FK: Ta.b''le A] FOREIGN KEY (I) REFERENCES [My.Tes''t Class].[Ta.b''le B](I);'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'My.Tes''t Class'; + + IF(SCHEMA_ID('My.Tes''t Class') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop [My.Tes''t Class]'; + END +END; +GO +CREATE PROC DropClassTests.[test drop class works if schema name is passed in unquoted] +AS +BEGIN + + EXEC('CREATE SCHEMA [My.Tes''t Class];'); + EXEC('CREATE TYPE [My.Tes''t Class].UDT FROM INT;'); + EXEC('CREATE TABLE [My.Tes''t Class].[Ta.b''le A](I INT PRIMARY KEY, OO [My.Tes''t Class].UDT);'); + EXEC('CREATE TABLE [My.Tes''t Class].[Ta.b''le B](I INT PRIMARY KEY CONSTRAINT [FK: Ta.b''le B] REFERENCES [My.Tes''t Class].[Ta.b''le A](I));'); + EXEC('CREATE XML SCHEMA COLLECTION [My.Tes''t Class].TestXMLSchema + AS'''';'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'My.Tes''t Class'; + + IF(SCHEMA_ID('My.Tes''t Class') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop [My.Tes''t Class]'; + END +END; +GO +CREATE PROC DropClassTests.[test drop class works if schema name is passed in quoted] +AS +BEGIN + + EXEC('CREATE SCHEMA [My.Tes''t Class];'); + EXEC('CREATE TYPE [My.Tes''t Class].UDT FROM INT;'); + EXEC('CREATE TABLE [My.Tes''t Class].[Ta.b''le A](I INT PRIMARY KEY, OO [My.Tes''t Class].UDT);'); + EXEC('CREATE TABLE [My.Tes''t Class].[Ta.b''le B](I INT PRIMARY KEY CONSTRAINT [FK: Ta.b''le B] REFERENCES [My.Tes''t Class].[Ta.b''le A](I));'); + EXEC('CREATE XML SCHEMA COLLECTION [My.Tes''t Class].TestXMLSchema + AS'''';'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass '[My.Tes''t Class]'; + + IF(SCHEMA_ID('My.Tes''t Class') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop [My.Tes''t Class]'; + END +END; +GO diff --git a/Tests/ExpectExceptionTests.class.sql b/Tests/ExpectExceptionTests.class.sql index 066784fb5..ca07ed2b4 100644 --- a/Tests/ExpectExceptionTests.class.sql +++ b/Tests/ExpectExceptionTests.class.sql @@ -186,7 +186,7 @@ BEGIN EXEC tSQLt.NewTestClass 'MyTestClass'; EXEC('CREATE PROC MyTestClass.TestExpectingNoException AS EXEC tSQLt.ExpectException;EXEC tSQLt.ExpectException;'); - EXEC tSQLt_testutil.AssertTestErrors 'MyTestClass.TestExpectingNoException','Each test can only contain one call to tSQLt.ExpectException.%'; + EXEC tSQLt_testutil.AssertTestErrors 'MyTestClass.TestExpectingNoException','%Each test can only contain one call to tSQLt.ExpectException.%'; END; GO CREATE PROCEDURE ExpectExceptionTests.[test expecting error number fails when unexpected error number is used] diff --git a/Tests/ExpectNoExceptionTests.class.sql b/Tests/ExpectNoExceptionTests.class.sql index fa4d77702..6fb2be2bf 100644 --- a/Tests/ExpectNoExceptionTests.class.sql +++ b/Tests/ExpectNoExceptionTests.class.sql @@ -16,22 +16,29 @@ BEGIN EXEC tSQLt_testutil.AssertTestFails 'MyTestClass.TestExpectingNoException'; END; GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE FUNCTION ExpectNoExceptionTests.[Return 42424242 prefix before ERROR_MESSAGE()]() +RETURNS TABLE +AS +RETURN + SELECT '42424242: '+ERROR_MESSAGE() FormattedError; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO CREATE PROCEDURE ExpectNoExceptionTests.[test tSQLt.ExpectNoException includes error information in fail message ] AS BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_GetFormattedErrorInfo', @FakeFunctionName = 'ExpectNoExceptionTests.[Return 42424242 prefix before ERROR_MESSAGE()]'; - EXEC tSQLt.NewTestClass 'MyTestClass'; - EXEC('CREATE PROC MyTestClass.TestExpectingNoException AS EXEC tSQLt.ExpectNoException;RAISERROR(''test error message'',16,10);'); + EXEC tSQLt.NewTestClass 'MyTestClass'; + EXEC('CREATE PROC MyTestClass.TestExpectingNoException AS EXEC tSQLt.ExpectNoException;RAISERROR(''test error message'',16,10);'); - DECLARE @ExpectedMessage NVARCHAR(MAX); - DECLARE @ProductMajorVersion INT; - EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; + DECLARE @ExpectedMessage NVARCHAR(MAX) = + 'Expected no error to be raised. Instead this error was encountered:'+CHAR(13)+CHAR(10)+ + '42424242: test error message'; - SET @ExpectedMessage = 'Expected no error to be raised. Instead this error was encountered:'+CHAR(13)+CHAR(10)+ - 'test error message[[]16,10]{'+ - CASE WHEN @ProductMajorVersion >= 14 THEN 'MyTestClass.' ELSE '' END+ - 'TestExpectingNoException,1}'; - EXEC tSQLt_testutil.AssertTestFails 'MyTestClass.TestExpectingNoException', @ExpectedMessage; + EXEC tSQLt_testutil.AssertTestFails 'MyTestClass.TestExpectingNoException', @ExpectedMessage; END; GO @@ -53,7 +60,7 @@ BEGIN EXEC tSQLt.NewTestClass 'MyTestClass'; EXEC('CREATE PROC MyTestClass.TestExpectingNoException AS EXEC tSQLt.ExpectNoException;EXEC tSQLt.ExpectNoException;'); - EXEC tSQLt_testutil.AssertTestErrors 'MyTestClass.TestExpectingNoException','Each test can only contain one call to tSQLt.ExpectNoException.%'; + EXEC tSQLt_testutil.AssertTestErrors 'MyTestClass.TestExpectingNoException','%Each test can only contain one call to tSQLt.ExpectNoException.%'; END; GO CREATE PROCEDURE ExpectNoExceptionTests.[test a ExpectNoException cannot follow an ExpectException] @@ -62,7 +69,7 @@ BEGIN EXEC tSQLt.NewTestClass 'MyTestClass'; EXEC('CREATE PROC MyTestClass.TestExpectingNoException AS EXEC tSQLt.ExpectException;EXEC tSQLt.ExpectNoException;'); - EXEC tSQLt_testutil.AssertTestErrors 'MyTestClass.TestExpectingNoException','tSQLt.ExpectNoException cannot follow tSQLt.ExpectException inside a single test.%'; + EXEC tSQLt_testutil.AssertTestErrors 'MyTestClass.TestExpectingNoException','%tSQLt.ExpectNoException cannot follow tSQLt.ExpectException inside a single test.%'; END; GO diff --git a/Tests/ExploratoryTests.class.sql b/Tests/ExploratoryTests.class.sql deleted file mode 100644 index 33b95af93..000000000 --- a/Tests/ExploratoryTests.class.sql +++ /dev/null @@ -1,23 +0,0 @@ -EXEC tSQLt.NewTestClass 'ExploratoryTests'; -GO -CREATE PROCEDURE ExploratoryTests.[test NULL can be CAST into any datatype] -AS -BEGIN - DECLARE @cmd NVARCHAR(MAX) = - ( - SELECT ',CAST(NULL AS '+QUOTENAME(SCHEMA_NAME(schema_id))+'.'+QUOTENAME(name)+')['+CAST(user_type_id AS NVARCHAR(MAX))+']' - FROM sys.types - WHERE is_user_defined = 0 - FOR XML PATH(''),TYPE - ).value('.','NVARCHAR(MAX)'); - SET @cmd = STUFF(@cmd,1,1,''); - SET @cmd = 'SELECT TOP(0) '+@cmd+' INTO ExploratoryTests.DataTypeTestTable;' - EXEC(@cmd); - SELECT * - INTO #Actual - FROM sys.columns - WHERE object_id = OBJECT_ID('ExploratoryTests.DataTypeTestTable') - AND name <> user_type_id - EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; -END; -GO diff --git a/Tests/FailTests.class.sql b/Tests/FailTests.class.sql index 28aa139d3..3f5bbb828 100644 --- a/Tests/FailTests.class.sql +++ b/Tests/FailTests.class.sql @@ -93,7 +93,7 @@ BEGIN DECLARE @TestResultMessage NVARCHAR(MAX); SELECT @TestResultMessage = Msg - FROM tSQLt.TestMessage; + FROM #TestMessage; DECLARE @ExpectedMessage NVARCHAR(MAX); SET @ExpectedMessage = '%Not really a failure - just seeing that fail works%'+CHAR(13)+CHAR(10)+'Warning: Uncommitable transaction detected!%' diff --git a/Tests/FakeTableTests.class.sql b/Tests/FakeTableTests.class.sql index 2926423bf..49ef0401a 100644 --- a/Tests/FakeTableTests.class.sql +++ b/Tests/FakeTableTests.class.sql @@ -377,7 +377,7 @@ BEGIN END; GO -CREATE PROC FakeTableTests.[test FakeTable works with ugly column and table names] +CREATE PROC FakeTableTests.[test FakeTable works with special characters in column and table names] AS BEGIN IF OBJECT_ID('dbo.[tst!@#$%^&*()_+ 1]') IS NOT NULL DROP TABLE dbo.[tst!@#$%^&*()_+ 1]; @@ -1032,3 +1032,14 @@ BEGIN END; GO +CREATE PROC FakeTableTests.[test FakeTable works if new name of original table requires quoting] +AS +BEGIN + CREATE TABLE FakeTableTests.TempTable1(i INT NULL); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_RenameObjectToUniqueName', @CommandToExecute = 'SET @NewName = ''A Name.Needs''''Quoting'';',@CallOriginal = 1; + + EXEC tSQLt.FakeTable @TableName = 'FakeTableTests.TempTable1'; + + EXEC tSQLt.AssertEqualsTableSchema @Expected = 'FakeTableTests.[A Name.Needs''Quoting]', @Actual = 'FakeTableTests.TempTable1'; +END; +GO diff --git a/Tests/InfoTests.class.sql b/Tests/InfoTests.class.sql index 60ea21bb2..e6d6b4821 100644 --- a/Tests/InfoTests.class.sql +++ b/Tests/InfoTests.class.sql @@ -159,5 +159,3 @@ BEGIN EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; ---TODO: --- include minimum supported version, like column with the lowest number that we run CI tests on (hardcoded) \ No newline at end of file diff --git a/Tests/Private_AssertNoSideEffectsTests.class.sql b/Tests/Private_AssertNoSideEffectsTests.class.sql new file mode 100644 index 000000000..549340b07 --- /dev/null +++ b/Tests/Private_AssertNoSideEffectsTests.class.sql @@ -0,0 +1,55 @@ +EXEC tSQLt.NewTestClass 'Private_AssertNoSideEffectsTests'; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE FUNCTION Private_AssertNoSideEffectsTests.[Faked_GenerateCommand]( + @BeforeExecutionObjectSnapshotTableName NVARCHAR(MAX), + @AfterExecutionObjectSnapshotTableName NVARCHAR(MAX) +) +RETURNS TABLE +AS +RETURN + SELECT @BeforeExecutionObjectSnapshotTableName + '<><><>' + @AfterExecutionObjectSnapshotTableName Command; +GO +CREATE PROCEDURE Private_AssertNoSideEffectsTests.[test Private_AssertNoSideEffects is using Private_CleanUpCmdHandler] +AS +BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_AssertNoSideEffects_GenerateCommand', @FakeFunctionName = 'Private_AssertNoSideEffectsTests.[Faked_GenerateCommand]'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUpCmdHandler'; + + EXEC tSQLt.Private_AssertNoSideEffects @BeforeExecutionObjectSnapshotTableName = 'BeforeTable', @AfterExecutionObjectSnapshotTableName = 'AfterTable', @TestResult = 'Result1', @TestMsg = 'Message1'; + + SELECT CleanUpCmd, TestResult, TestMsg INTO #Actual FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES ('BeforeTable<><><>AfterTable','Result1','Message1'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_AssertNoSideEffectsTests.[test Private_AssertNoSideEffects is using Private_CleanUpCmdHandler with @TestResult and @TestMsg as OUTPUT parameters] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUpCmdHandler', @CommandToExecute = 'SET @TestResult = ''Altered Test Result'';SET @TestMsg = ''Altered Test Message'';'; + + DECLARE @TestResult NVARCHAR(MAX) = 'Result1'; + DECLARE @TestMessage NVARCHAR(MAX) = 'Message1'; + + EXEC tSQLt.Private_AssertNoSideEffects @BeforeExecutionObjectSnapshotTableName = 'BeforeTable', @AfterExecutionObjectSnapshotTableName = 'AfterTable', @TestResult = @TestResult OUT, @TestMsg = @TestMessage OUT; + + SELECT @TestResult TestResult, @TestMessage TestMsg INTO #Actual + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES ('Altered Test Result','Altered Test Message'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO diff --git a/Tests/Private_CleanUpCmdHandlerTests.class.sql b/Tests/Private_CleanUpCmdHandlerTests.class.sql new file mode 100644 index 000000000..a2caa9613 --- /dev/null +++ b/Tests/Private_CleanUpCmdHandlerTests.class.sql @@ -0,0 +1,44 @@ +EXEC tSQLt.NewTestClass 'Private_CleanUpCmdHandlerTests'; +GO +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE FUNCTION Private_CleanUpCmdHandlerTests.[return 42134213 if correct error]() +RETURNS TABLE +AS +RETURN + SELECT '9999' + ERROR_MESSAGE() FormattedError; +GO +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE FUNCTION Private_CleanUpCmdHandlerTests.[return 42424242+@NewMessage, @NewResult]( + @PrevMessage NVARCHAR(MAX), + @PrevResult NVARCHAR(MAX), + @NewMessage NVARCHAR(MAX), + @NewResult NVARCHAR(MAX) +) +RETURNS TABLE +AS +RETURN + SELECT @PrevResult+':7777:'+@NewMessage Message, @NewResult Result +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpCmdHandlerTests.[test is using the two error functions correctly] +AS +BEGIN + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_GetFormattedErrorInfo', @FakeFunctionName = 'Private_CleanUpCmdHandlerTests.[return 42134213 if correct error]'; + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Private_HandleMessageAndResult', @FakeFunctionName = 'Private_CleanUpCmdHandlerTests.[return 42424242+@NewMessage, @NewResult]'; + + DECLARE @TestResult NVARCHAR(MAX) = 'PrevResult'; + DECLARE @TestMessage NVARCHAR(MAX) = 'PrevMessage'; + EXEC tSQLt.Private_CleanUpCmdHandler @CleanUpCmd = 'RAISERROR(''ACleanUpError'',16,10);', @TestResult=@TestResult OUT, @TestMsg = @TestMessage OUT, @ResultInCaseOfError = 'NewResult'; + + SELECT @TestMessage Message, @TestResult Result INTO #Actual; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES('PrevResult:7777:Error during clean up: (9999ACleanUpError)','NewResult'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO diff --git a/Tests/Private_CleanUpTests.class.sql b/Tests/Private_CleanUpTests.class.sql new file mode 100644 index 000000000..2a07b968e --- /dev/null +++ b/Tests/Private_CleanUpTests.class.sql @@ -0,0 +1,123 @@ +EXEC tSQLt.NewTestClass 'Private_CleanUpTests'; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpTests.[test calls tSQLt.UndoTestDoubles with @Force=0] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; + + EXEC tSQLt.Private_CleanUp @FullTestName = NULL, @Result = NULL, @ErrorMsg = NULL; + + SELECT _id_, Force INTO #Actual FROM tSQLt.UndoTestDoubles_SpyProcedureLog; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,0); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpTests.[test calls tSQLt.Private_NoTransactionHandleTables with @Action='Reset'] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + + EXEC tSQLt.Private_CleanUp @FullTestName = NULL, @Result = NULL, @ErrorMsg = NULL; + + SELECT _id_, Action INTO #Actual FROM tSQLt.Private_NoTransactionHandleTables_SpyProcedureLog; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1, 'Reset'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpTests.[test calls tSQLt.Private_CleanUpCmdHandler for only UndoTestDoubles and HandleTables in the correct order] +AS +BEGIN + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_CleanUpCmdHandler'; + + EXEC tSQLt.Private_CleanUp @FullTestName = NULL, @Result = NULL, @ErrorMsg = NULL; + + SELECT _id_, CleanUpCmd INTO #Actual FROM tSQLt.Private_CleanUpCmdHandler_SpyProcedureLog; + SELECT TOP(0) A._id_, A.CleanUpCmd AS [%CleanUpCmd] INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'%tSQLt.Private_NoTransactionHandleTables%'),(2,'%tSQLt.UndoTestDoubles%'); + + SELECT * INTO #Compare + FROM( + SELECT '>' _R_,* FROM #Actual AS A WHERE NOT EXISTS(SELECT 1 FROM #Expected E WHERE A._id_ = E._id_ AND A.CleanUpCmd LIKE E.[%CleanUpCmd]) + UNION ALL + SELECT '<' _R_,* FROM #Expected AS E WHERE NOT EXISTS(SELECT 1 FROM #Actual A WHERE A._id_ = E._id_ AND A.CleanUpCmd LIKE E.[%CleanUpCmd]) + )X + EXEC tSQLt.AssertEmptyTable @TableName = '#Compare'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpTests.[test UndoTestDoubles error message is appended to @ErrorMsg] +AS +BEGIN + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles', @CommandToExecute = 'RAISERROR(''some cleanup error'',16, 10)'; + + DECLARE @ErrorMsg NVARCHAR(MAX) = 'previous error'; + EXEC tSQLt.Private_CleanUp @FullTestName = NULL, @Result = NULL, @ErrorMsg = @ErrorMsg OUT; + + EXEC tSQLt.AssertLike @ExpectedPattern = 'previous error%some cleanup error%', @Actual = @ErrorMsg; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpTests.[test UndoTestDoubles error causes @Result to be set to Abort] +AS +BEGIN + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles', @CommandToExecute = 'RAISERROR(''some cleanup error'',16, 10)'; + + DECLARE @Result NVARCHAR(MAX) = 'NOT ERROR'; + EXEC tSQLt.Private_CleanUp @FullTestName = NULL, @Result = @Result OUT, @ErrorMsg = NULL; + + EXEC tSQLt.AssertEqualsString @Expected = 'Abort', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpTests.[test HandleTables error is appended to @ErrorMsg] +AS +BEGIN + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables', @CommandToExecute = 'RAISERROR(''some cleanup error'',16, 10)'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; + + DECLARE @ErrorMsg NVARCHAR(MAX) = 'previous error'; + EXEC tSQLt.Private_CleanUp @FullTestName = NULL, @Result = NULL, @ErrorMsg = @ErrorMsg OUT; + + EXEC tSQLt.AssertLike @ExpectedPattern = 'previous error%some cleanup error%', @Actual = @ErrorMsg; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_CleanUpTests.[test HandleTables error causes @Result to be set to FATAL] +AS +BEGIN + + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables', @CommandToExecute = 'RAISERROR(''some cleanup error'',16, 10)'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; + + DECLARE @Result NVARCHAR(MAX) = 'NOT ERROR'; + EXEC tSQLt.Private_CleanUp @FullTestName = NULL, @Result = @Result OUT, @ErrorMsg = NULL; + + EXEC tSQLt.AssertEqualsString @Expected = 'FATAL', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO diff --git a/Tests/Private_GetFormattedErrorInfoTests.class.sql b/Tests/Private_GetFormattedErrorInfoTests.class.sql new file mode 100644 index 000000000..c57fc1929 --- /dev/null +++ b/Tests/Private_GetFormattedErrorInfoTests.class.sql @@ -0,0 +1,90 @@ +EXEC tSQLt.NewTestClass 'Private_GetFormattedErrorInfoTests'; +GO +CREATE PROCEDURE Private_GetFormattedErrorInfoTests.[test does not return null] +AS +BEGIN + DECLARE @FormattedError NVARCHAR(MAX) = (SELECT FormattedError FROM tSQLt.Private_GetFormattedErrorInfo()); + + EXEC tSQLt.AssertEqualsString @Expected = 'Message: | Procedure: | Severity, State: , | Number: ', @Actual = @FormattedError; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_GetFormattedErrorInfoTests.[test returns the ERROR information formatted correctly] +AS +BEGIN + DECLARE @FormattedError NVARCHAR(MAX); + + BEGIN TRY + EXEC ('RAISERROR(''my test message'', 15, 11);'); + END TRY + BEGIN CATCH + SET @FormattedError = (SELECT FormattedError FROM tSQLt.Private_GetFormattedErrorInfo()); + END CATCH; + + EXEC tSQLt.AssertEqualsString @Expected = 'Message: my test message | Procedure: (1) | Severity, State: 15, 11 | Number: 50000', @Actual = @FormattedError; +END +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_GetFormattedErrorInfoTests.[test returns the correct ERROR number] +AS +BEGIN + DECLARE @FormattedError NVARCHAR(MAX); + + BEGIN TRY + EXEC ('RAISERROR (13042,14,13);'); + END TRY + BEGIN CATCH + SET @FormattedError = (SELECT FormattedError FROM tSQLt.Private_GetFormattedErrorInfo()); + END CATCH; + + EXEC tSQLt.AssertLike @ExpectedPattern = '%| Number: 13042', @Actual = @FormattedError; +END +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_GetFormattedErrorInfoTests.[test returns the correct ERROR procedure name and line number] +AS +BEGIN + DECLARE @FormattedError NVARCHAR(MAX); + + BEGIN TRY + EXEC ('/*Line 1*/CREATE PROCEDURE Private_GetFormattedErrorInfoTests.myInnerError + /*Line 2*/AS + /*Line 3*/BEGIN + /*Line 4*/ RAISERROR (13042,14,13); + /*Line 5*/END;'); + EXEC ('Private_GetFormattedErrorInfoTests.myInnerError'); + END TRY + BEGIN CATCH + SET @FormattedError = (SELECT FormattedError FROM tSQLt.Private_GetFormattedErrorInfo()); + END CATCH; + + EXEC tSQLt.AssertLike @ExpectedPattern = '%| Procedure: %myInnerError (4) |%', @Actual = @FormattedError; +END +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_GetFormattedErrorInfoTests.[test returns the correct ERROR procedure name and line number for #tempProcedure] +AS +BEGIN + DECLARE @FormattedError NVARCHAR(MAX); + + BEGIN TRY + EXEC ('/*Line 1*/CREATE PROCEDURE #myInnerError + /*Line 2*/AS + /*Line 3*/BEGIN + /*Line 4*/ RAISERROR (13042,14,13); + /*Line 5*/END;'); + EXEC #myInnerError; + END TRY + BEGIN CATCH + SET @FormattedError = (SELECT FormattedError FROM tSQLt.Private_GetFormattedErrorInfo()); + END CATCH; + + EXEC tSQLt.AssertLike @ExpectedPattern = '%| Procedure: #myInnerError% (4) |%', @Actual = @FormattedError; +END +GO +/*-----------------------------------------------------------------------------------------------*/ +GO diff --git a/Tests/Private_HandleMessageAndResultTests.class.sql b/Tests/Private_HandleMessageAndResultTests.class.sql new file mode 100644 index 000000000..ca0bef266 --- /dev/null +++ b/Tests/Private_HandleMessageAndResultTests.class.sql @@ -0,0 +1,174 @@ +EXEC tSQLt.NewTestClass 'Private_HandleMessageAndResultTests'; +GO +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns appropriately formatted Message if the first three parameters are null] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult (NULL, NULL, NULL, DEFAULT)); + + EXEC tSQLt.AssertEqualsString @Expected = ' [Result: ] || ', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @NewMessage in appropriately formatted Message if only @NewMessage is valued] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult (NULL, NULL, 'a random message', DEFAULT)); + + EXEC tSQLt.AssertEqualsString @Expected = ' [Result: ] || a random message', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @PrevMessage in appropriately formatted Message if only @PrevMessage is valued] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult ('another random message', NULL, NULL, DEFAULT)); + + EXEC tSQLt.AssertEqualsString @Expected = 'another random message [Result: ] || ', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns appropriate Message if only @PrevResult is valued] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult (NULL, 'ResultOne', NULL, DEFAULT)); + EXEC tSQLt.AssertEqualsString @Expected = ' [Result: ResultOne] || ', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns in Message for each of the first three parameters if it is an empty string] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult ('', '', '', DEFAULT)); + + EXEC tSQLt.AssertEqualsString @Expected = ' [Result: ] || ', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns empty Message if the first three parameters are white space only strings] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult (CHAR(9), CHAR(9)+' '+CHAR(9), ' '+CHAR(9)+' ', DEFAULT)); + EXEC tSQLt.AssertEqualsString @Expected = ' [Result: ] || ', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @NewResult as Result] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_Results'; + EXEC ('INSERT INTO tSQLt.Private_Results(Result, Severity)VALUES(''SomeResult'',42)'); + + DECLARE @Result NVARCHAR(MAX); + SET @Result = (SELECT Result FROM tSQLt.Private_HandleMessageAndResult (DEFAULT, DEFAULT, DEFAULT, 'SomeResult')); + EXEC tSQLt.AssertEqualsString @Expected = 'SomeResult', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @PrevResult as Result if @NewResult is less severe] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_Results'; + EXEC ('INSERT INTO tSQLt.Private_Results(Result, Severity)VALUES(''SomeSevereResult'',7),(''SomeLessSevereResult'',3)'); + + DECLARE @Result NVARCHAR(MAX); + SET @Result = (SELECT Result FROM tSQLt.Private_HandleMessageAndResult (DEFAULT, 'SomeSevereResult', DEFAULT, 'SomeLessSevereResult')); + EXEC tSQLt.AssertEqualsString @Expected = 'SomeSevereResult', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @PrevResult as Result if @NewResult is less severe if there are other values in Private_Results] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_Results'; + EXEC ('INSERT INTO tSQLt.Private_Results(Result, Severity)VALUES(''aaa'',10),(''SomeSevereResult'',7),(''SomeLessSevereResult'',3)'); + + DECLARE @Result NVARCHAR(MAX); + SET @Result = (SELECT Result FROM tSQLt.Private_HandleMessageAndResult (DEFAULT, 'SomeSevereResult', DEFAULT, 'SomeLessSevereResult')); + EXEC tSQLt.AssertEqualsString @Expected = 'SomeSevereResult', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @PrevResult if @NewResult is not known] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_Results'; + EXEC ('INSERT INTO tSQLt.Private_Results(Result, Severity)VALUES(''SomePreviousResult'',3)'); + + DECLARE @Result NVARCHAR(MAX); + SET @Result = (SELECT Result FROM tSQLt.Private_HandleMessageAndResult (DEFAULT, 'SomePreviousResult', DEFAULT, 'SomeUnknownResult')); + EXEC tSQLt.AssertEqualsString @Expected = 'SomePreviousResult', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @NewResult if @PrevResult is not known] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_Results'; + EXEC ('INSERT INTO tSQLt.Private_Results(Result, Severity)VALUES(''SomeNewResult'',3)'); + + DECLARE @Result NVARCHAR(MAX); + SET @Result = (SELECT Result FROM tSQLt.Private_HandleMessageAndResult (DEFAULT, 'SomeUnknownResult', DEFAULT, 'SomeNewResult')); + EXEC tSQLt.AssertEqualsString @Expected = 'SomeNewResult', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns only the @NewMessage if @PrevMessage is empty and @PrevResult is Success] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult ('', 'Success', 'this is the new message', DEFAULT)); + EXEC tSQLt.AssertEqualsString @Expected = 'this is the new message', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns only the @NewMessage if @PrevMessage is NULL and @PrevResult is Success] +AS +BEGIN + DECLARE @Message NVARCHAR(MAX); + SET @Message = (SELECT Message FROM tSQLt.Private_HandleMessageAndResult (NULL, 'Success', 'this is the new message', DEFAULT)); + EXEC tSQLt.AssertEqualsString @Expected = 'this is the new message', @Actual = @Message; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROCEDURE Private_HandleMessageAndResultTests.[test returns @NewResult if @PrevResult is NULL] +AS +BEGIN + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_Results'; + EXEC ('INSERT INTO tSQLt.Private_Results(Result, Severity)VALUES(''SomeNewResult'',3)'); + + DECLARE @Result NVARCHAR(MAX); + SET @Result = (SELECT Result FROM tSQLt.Private_HandleMessageAndResult (DEFAULT, NULL, DEFAULT, 'SomeNewResult')); + EXEC tSQLt.AssertEqualsString @Expected = 'SomeNewResult', @Actual = @Result; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + diff --git a/Tests/Private_InitTests.class.sql b/Tests/Private_InitTests.class.sql index 1b21021bf..662db364b 100644 --- a/Tests/Private_InitTests.class.sql +++ b/Tests/Private_InitTests.class.sql @@ -39,42 +39,3 @@ RETURNS TABLE AS RETURN SELECT '1234' Version, '1234' ClrVersion,CAST(98.76 AS NUMERIC(10,2)) SqlVersion, CAST(98.76 AS NUMERIC(10,2)) InstalledOnSqlVersion, NULL SqlBuild, CAST(NULL AS NVARCHAR(MAX)) SqlEdition; GO -CREATE PROCEDURE Private_InitTests.[test Private_Init causes a NULL row to be inserted in tSQLt.Private_NullCellTable if empty] -AS -BEGIN - EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.EnableExternalAccess'; - EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Info', @FakeFunctionName = 'Private_InitTests.[matching versions]'; - - EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NullCellTable'; - EXEC tSQLt.ApplyTrigger @TableName = 'tSQLt.Private_NullCellTable', @TriggerName= 'tSQLt.Private_NullCellTable_StopModifications'; - - EXEC tSQLt.Private_Init; - - SELECT * INTO #Actual FROM tSQLt.Private_NullCellTable; - SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; - INSERT INTO #Expected VALUES (NULL); - - EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; -END; -GO - -CREATE PROCEDURE Private_InitTests.[test Private_Init does not insert a second row into tSQLt.Private_NullCellTable when called repeatedly] -AS -BEGIN - EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.EnableExternalAccess'; - EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt.Info', @FakeFunctionName = 'Private_InitTests.[matching versions]'; - - EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NullCellTable'; - EXEC tSQLt.ApplyTrigger @TableName = 'tSQLt.Private_NullCellTable', @TriggerName= 'tSQLt.Private_NullCellTable_StopModifications'; - - EXEC tSQLt.Private_Init; - EXEC tSQLt.Private_Init; - - SELECT * INTO #Actual FROM tSQLt.Private_NullCellTable; - SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; - - INSERT INTO #Expected VALUES (NULL); - - EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; -END; -GO \ No newline at end of file diff --git a/Tests/Private_MarktSQLtTempObjectTests.class.sql b/Tests/Private_MarktSQLtTempObjectTests.class.sql index 205caa035..097aa710e 100644 --- a/Tests/Private_MarktSQLtTempObjectTests.class.sql +++ b/Tests/Private_MarktSQLtTempObjectTests.class.sql @@ -2,13 +2,14 @@ EXEC tSQLt.NewTestClass 'Private_MarktSQLtTempObjectTests'; GO CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] @ObjectName NVARCHAR(MAX), - @ObjectType NVARCHAR(MAX) + @ObjectType NVARCHAR(MAX), + @NewNameOfOriginalObject NVARCHAR(MAX) = 'ARandomString' AS BEGIN EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = @ObjectName, @ObjectType = @ObjectType, - @NewNameOfOriginalObject = 'ARandomString'; + @NewNameOfOriginalObject = @NewNameOfOriginalObject; SELECT name, CAST(value AS NVARCHAR(MAX)) value INTO #Actual @@ -19,8 +20,8 @@ BEGIN SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; - INSERT INTO #Expected VALUES('tSQLt.IsTempObject', '1'), - ('tSQLt.Private_TestDouble_OrgObjectName','ARandomString'); + INSERT INTO #Expected VALUES('tSQLt.IsTempObject', '1'); + INSERT INTO #Expected SELECT 'tSQLt.Private_TestDouble_OrgObjectName',@NewNameOfOriginalObject WHERE @NewNameOfOriginalObject IS NOT NULL; EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; @@ -110,6 +111,103 @@ BEGIN INSERT INTO #Expected VALUES('BIT'); EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test doesn't set tSQLt.Private_TestDouble_OrgObjectName if @NewNameOfOriginalObject is NULL] +AS +BEGIN + CREATE TABLE Private_MarktSQLtTempObjectTests.TempTable1(i INT NOT NULL); + EXEC tSQLt.Private_MarktSQLtTempObject + @ObjectName = 'Private_MarktSQLtTempObjectTests.TempTable1', + @ObjectType = N'TABLE', + @NewNameOfOriginalObject = NULL; + + SELECT * + INTO #Actual + FROM sys.extended_properties AS EP + WHERE EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = OBJECT_ID('Private_MarktSQLtTempObjectTests.TempTable1') + AND EP.name = 'tSQLt.Private_TestDouble_OrgObjectName'; + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test doesn't set tSQLt.Private_TestDouble_OrgObjectName if @NewNameOfOriginalObject is NULL for child objects] +AS +BEGIN + CREATE TABLE Private_MarktSQLtTempObjectTests.TempTable1(i INT NOT NULL CONSTRAINT TempConstraint1 PRIMARY KEY); + EXEC tSQLt.Private_MarktSQLtTempObject + @ObjectName = 'Private_MarktSQLtTempObjectTests.TempConstraint1', + @ObjectType = N'CONSTRAINT', + @NewNameOfOriginalObject = NULL; + SELECT * + INTO #Actual + FROM sys.extended_properties AS EP + WHERE EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = OBJECT_ID('Private_MarktSQLtTempObjectTests.TempConstraint1') + AND EP.name = 'tSQLt.Private_TestDouble_OrgObjectName'; + + EXEC tSQLt.AssertEmptyTable '#Actual'; END; -GO \ No newline at end of file +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test defaults to not setting tSQLt.Private_TestDouble_OrgObjectName] +AS +BEGIN + CREATE TABLE Private_MarktSQLtTempObjectTests.TempTable1(i INT NOT NULL); + EXEC tSQLt.Private_MarktSQLtTempObject + @ObjectName = 'Private_MarktSQLtTempObjectTests.TempTable1', + @ObjectType = N'TABLE'; + + SELECT * + INTO #Actual + FROM sys.extended_properties AS EP + WHERE EP.class_desc = 'OBJECT_OR_COLUMN' + AND EP.major_id = OBJECT_ID('Private_MarktSQLtTempObjectTests.TempTable1') + AND EP.name = 'tSQLt.Private_TestDouble_OrgObjectName'; + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test can mark an object with a schema and object name which include single quotes] +AS +BEGIN + EXEC('CREATE SCHEMA [Private_Mark''tSQLtTempObjectTests];'); + EXEC('CREATE PROCEDURE [Private_Mark''tSQLtTempObjectTests].[TempProcedure''1] AS RETURN;'); + EXEC Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName = '[Private_Mark''tSQLtTempObjectTests].[TempProcedure''1]', + @ObjectType = N'PROCEDURE'; +END +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test can mark an object with a schema and object name which includes a single quotes, spaces, and dots] +AS +BEGIN + EXEC('CREATE SCHEMA [P.rivate_Mark''tSQLtTempObj ectTests];'); + EXEC('CREATE PROCEDURE [P.rivate_Mark''tSQLtTempObj ectTests].[Tem.pPr ocedure''1] AS RETURN;'); + EXEC Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName = '[P.rivate_Mark''tSQLtTempObj ectTests].[Tem.pPr ocedure''1]', + @ObjectType = N'PROCEDURE'; +END +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_MarktSQLtTempObjectTests.[test can mark a table with a schema and object name which includes a single quotes, spaces, and dots] +AS +BEGIN + EXEC('CREATE SCHEMA [P.rivate_Mark''tSQLtTempObj ectTests];'); + EXEC('CREATE TABLE [P.rivate_Mark''tSQLtTempObj ectTests].[Tem.pPr ocedure''1] (AA INT);'); + EXEC Private_MarktSQLtTempObjectTests.[assert creates two extended properties on object] + @ObjectName = '[P.rivate_Mark''tSQLtTempObj ectTests].[Tem.pPr ocedure''1]', + @ObjectType = N'TABLE', + @NewNameOfOriginalObject = NULL +END +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + + + diff --git a/Tests/Private_NoTransactionHandleTableTests.class.sql b/Tests/Private_NoTransactionHandleTableTests.class.sql new file mode 100644 index 000000000..5030081cb --- /dev/null +++ b/Tests/Private_NoTransactionHandleTableTests.class.sql @@ -0,0 +1,659 @@ +EXEC tSQLt.NewTestClass 'Private_NoTransactionHandleTableTests'; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test errors if Action is not an acceptable value] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT); + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%Invalid @Action parameter value.%', @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Unexpected Action', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test creates new table and saves its name in #TableBackupLog if Action is Save] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + DECLARE @BackupName NVARCHAR(MAX) = (SELECT BackupName FROM #TableBackupLog WHERE OriginalName = 'Private_NoTransactionHandleTableTests.Table1'); + + EXEC tSQLt.AssertEqualsTableSchema @Expected = 'Private_NoTransactionHandleTableTests.Table1', @Actual = @BackupName; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test calls tSQLt.Private_MarktSQLtTempObject on new object] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_MarktSQLtTempObject'; + TRUNCATE TABLE tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog;--Quirkiness of testing the framework that you use to run the test + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + DECLARE @BackupName NVARCHAR(MAX) = (SELECT BackupName FROM #TableBackupLog WHERE OriginalName = 'Private_NoTransactionHandleTableTests.Table1'); + + SELECT ObjectName, ObjectType, NewNameOfOriginalObject + INTO #Actual + FROM tSQLt.Private_MarktSQLtTempObject_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES(ISNULL(@BackupName,'Backup table not found.'), N'TABLE', NULL); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test does not create a backup table if @Action is Save and the @TableAction is Truncate] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + SELECT object_id, SCHEMA_NAME(schema_id) [schema_name], name INTO #Before FROM sys.tables; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Truncate'; + + SELECT * INTO #Actual + FROM ( + ( + SELECT 'Extra'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + EXCEPT + SELECT 'Extra'[?],* FROM #BEFORE + ) + UNION ALL + ( + SELECT 'Missing'[?],* FROM #BEFORE + EXCEPT + SELECT 'Missing'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + ) + ) X; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test does not create a backup table if @Action is Save and the @TableAction is Ignore] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + SELECT object_id, SCHEMA_NAME(schema_id) [schema_name], name INTO #Before FROM sys.tables; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Ignore'; + + SELECT * INTO #Actual + FROM ( + ( + SELECT 'Extra'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + EXCEPT + SELECT 'Extra'[?],* FROM #BEFORE + ) + UNION ALL + ( + SELECT 'Missing'[?],* FROM #BEFORE + EXCEPT + SELECT 'Missing'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + ) + ) X; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test calls tSQLt.RemoveObject if @Action is Save and @TableAction is Hide] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.RemoveObject'; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Hide'; + + SELECT ObjectName INTO #Actual FROM tSQLt.RemoveObject_SpyProcedureLog; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('Private_NoTransactionHandleTableTests.Table1'); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test does not create a backup table if @Action is Save and @TableAction is Hide] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + DECLARE @OriginalObjectId INT = OBJECT_ID('Private_NoTransactionHandleTableTests.Table1'); + + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + SELECT object_id, SCHEMA_NAME(schema_id) [schema_name], name INTO #Before FROM sys.tables WHERE object_id <> @OriginalObjectId; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Hide'; + + SELECT * INTO #Actual + FROM ( + ( + SELECT 'Extra'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables WHERE object_id <> @OriginalObjectId + EXCEPT + SELECT 'Extra'[?],* FROM #BEFORE + ) + UNION ALL + ( + SELECT 'Missing'[?],* FROM #BEFORE + EXCEPT + SELECT 'Missing'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables WHERE object_id <> @OriginalObjectId + ) + ) X; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test errors if @Action is Save and @TableAction is not an acceptable value] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.SomeTable(i INT); + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%Invalid @TableAction parameter value.%', @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.SomeTable', @TableAction = 'Unacceptable'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +--[@tSQLt:MaxSqlMajorVersion](13) +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test augments any internal error with ' tSQLt is in an unknown state: Stopping execution. (<=2016)'] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.SomeTable(i INT); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.RemoveObject', @CommandToExecute='RAISERROR(''SOME INTERNAL ERROR.'',15,11)'; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'tSQLt is in an unknown state: Stopping execution. (SOME INTERNAL ERROR. | Procedure: RemoveObject | Line: 1)', @ExpectedSeverity = 15, @ExpectedState = 11; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.SomeTable', @TableAction = 'Hide'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +--[@tSQLt:MinSqlMajorVersion](14) +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test augments any internal error with ' tSQLt is in an unknown state: Stopping execution. (>2016)'] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.SomeTable(i INT); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.RemoveObject', @CommandToExecute='RAISERROR(''SOME INTERNAL ERROR.'',15,11)'; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'tSQLt is in an unknown state: Stopping execution. (SOME INTERNAL ERROR. | Procedure: tSQLt.RemoveObject | Line: 1)', @ExpectedSeverity = 15, @ExpectedState = 11; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.SomeTable', @TableAction = 'Hide'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test errors if the table does not exist] +AS +BEGIN + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%Nonexistent.Table does not exist%'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'someaction', @FullTableName = 'Nonexistent.Table', @TableAction = 'sometableaction'; +END; +GO +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test copies data from backup into the emptied original table if @Action is Reset and @TableAction is Restore] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + TRUNCATE TABLE Private_NoTransactionHandleTableTests.Table1; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + SELECT * INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test deletes original table data before restoring] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + TRUNCATE TABLE Private_NoTransactionHandleTableTests.Table1; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES(4, 'abcdef'),(6, 'dd'),(7, 'khdf'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + SELECT * INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test can restore table with identity column] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES('a'),('bb'),('cdce'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + SELECT * INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test can restore table with computed column] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, computedcolumn AS UPPER(col1), col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES(1,'a'),(2,'bb'),(3,'cdce'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + SELECT Id, col1 INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test Reset @Action with unknown @TableAction causes error] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.SomeTable(i INT); + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%Invalid @TableAction parameter value.%', @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.SomeTable', @TableAction = 'Unacceptable'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test Reset @Action with truncate @TableAction deletes all data from the table] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT IDENTITY (1,1), col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES('a'),('bb'),('cdce'); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Truncate'; + + EXEC tSQLt.AssertEmptyTable @TableName = 'Private_NoTransactionHandleTableTests.Table1'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test does not change the data if @Action is Reset and @TableAction Ignore] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES(4, 'jkdf'),(5, 'adfad'),(6, 'yuio'); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Ignore'; + + SELECT TOP(0) A.* INTO #Expected FROM Private_NoTransactionHandleTableTests.Table1 A RIGHT JOIN Private_NoTransactionHandleTableTests.Table1 X ON 1=0; + + INSERT INTO #Expected VALUES(1, 'a'),(2, 'bb'),(3, 'cdce'),(4, 'jkdf'),(5, 'adfad'),(6, 'yuio'); + + EXEC tSQLt.AssertEqualsTable @Expected = '#Expected', @Actual = 'Private_NoTransactionHandleTableTests.Table1'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test does not error and does not restore if @Action is Reset and @TableAction Hide] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Hide'; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Hide'; + + SELECT * INTO #Actual FROM sys.tables WHERE object_id = OBJECT_ID('Private_NoTransactionHandleTableTests.Table1'); + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Restore, @Action Save, Save: the second Save does nothing] +AS +BEGIN + /* No additional temp objects are created, and any data in the backup table is unchanged (even if deleted for example) */ + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + SELECT object_id, SCHEMA_NAME(schema_id) [schema_name], name INTO #Before FROM sys.tables; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = 'Private_NoTransactionHandleTableTests.Table1', @TableAction = 'Restore'; + + SELECT * INTO #Actual + FROM ( + ( + SELECT 'Extra'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + EXCEPT + SELECT 'Extra'[?],* FROM #Before + ) + UNION ALL + ( + SELECT 'Missing'[?],* FROM #Before + EXCEPT + SELECT 'Missing'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + ) + ) X; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Restore, @Action Save, Save: the second Save does nothing to the existing backup table] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + DECLARE @OriginalBackupTableName NVARCHAR(MAX) = (SELECT BackupName FROM #TableBackupLog WHERE OriginalName = '[Private_NoTransactionHandleTableTests].[Table1]'); + EXEC('DELETE FROM '+@OriginalBackupTableName+';'); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + + + EXEC tSQLt.AssertEmptyTable @TableName = @OriginalBackupTableName; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[Eclipse #TableBackupLog and execute Save & Reset] +AS +BEGIN + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; +END; +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Restore, @Action Save, Save(Eclipsed), Reset(Eclipsed), Reset: gets back to point A] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + /*Point A*/ + SELECT * INTO #Expected FROM Private_NoTransactionHandleTableTests.Table1; + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC Private_NoTransactionHandleTableTests.[Eclipse #TableBackupLog and execute Save & Reset]; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + + SELECT * INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Restore, @Action Save, Save, Reset, Reset: gets back to point A] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + /*Point A*/ + SELECT * INTO #Expected FROM Private_NoTransactionHandleTableTests.Table1; + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + + SELECT * INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[Eclipse #TableBackupLog and execute Save] +AS +BEGIN + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; +END; +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Restore, @Action Save, Save(Eclipsed), Reset: gets back to point A] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + /*Point A*/ + SELECT * INTO #Expected FROM Private_NoTransactionHandleTableTests.Table1; + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC Private_NoTransactionHandleTableTests.[Eclipse #TableBackupLog and execute Save]; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + + SELECT * INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Restore, @Action Save, Reset, Reset: gets back to point A] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + /*Point A*/ + SELECT * INTO #Expected FROM Private_NoTransactionHandleTableTests.Table1; + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + + SELECT * INTO #Actual FROM Private_NoTransactionHandleTableTests.Table1; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Restore, @Action Save, Reset: removes entry from #TableBackupLog] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + CREATE TABLE #TableBackupLog(OriginalName NVARCHAR(MAX), BackupName NVARCHAR(MAX)); + INSERT INTO #TableBackupLog VALUES ('SomeUnrelatedTable','tSQLt_SomeUnrelatedTable_Temp'); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Restore'; + + SELECT TOP(0) A.* INTO #Expected FROM #TableBackupLog A RIGHT JOIN #TableBackupLog X ON 1=0; + + INSERT INTO #Expected VALUES ('SomeUnrelatedTable','tSQLt_SomeUnrelatedTable_Temp'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#TableBackupLog'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Hide, @Action Save, and table has be previously hidden, do nothing] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + + EXEC tSQLt.RemoveObject @ObjectName = '[Private_NoTransactionHandleTableTests].[Table1]'; /* Hide here */ + SELECT object_id, SCHEMA_NAME(schema_id) [schema_name], name INTO #Before FROM sys.tables; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Hide'; + + SELECT * INTO #Actual + FROM ( + ( + SELECT 'Extra'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + EXCEPT + SELECT 'Extra'[?],* FROM #Before + ) + UNION ALL + ( + SELECT 'Missing'[?],* FROM #Before + EXCEPT + SELECT 'Missing'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + ) + ) X; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Truncate, @Action Save, Save, Reset, Save, Reset, Reset: table is empty] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Truncate'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (4,'e'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Truncate'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (5,'d'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Truncate'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (6,'f'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Truncate'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (7,'t'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Truncate'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (8,'g'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Truncate'; + + EXEC tSQLt.AssertEmptyTable @TableName = 'Private_NoTransactionHandleTableTests.Table1'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Ignore, @Action Save, Save, Reset, Reset: table is unaltered] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Ignore'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (4,'e'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Ignore'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (6,'f'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Ignore'; + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (7,'t'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Ignore'; + + SELECT TOP(0) A.* INTO #Expected FROM Private_NoTransactionHandleTableTests.Table1 A RIGHT JOIN Private_NoTransactionHandleTableTests.Table1 X ON 1=0; + + INSERT INTO #Expected VALUES (2,'c'), (3,'a'), (4,'e'), (6,'f'), (7,'t'); + + EXEC tSQLt.AssertEqualsTable @Expected ='#Expected', @Actual = 'Private_NoTransactionHandleTableTests.Table1'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Hide, @Action Save, Save: do nothing on second save] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Hide'; + + SELECT object_id, SCHEMA_NAME(schema_id) [schema_name], name INTO #Before FROM sys.tables; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Hide'; + + SELECT * INTO #Actual + FROM ( + ( + SELECT 'Extra'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + EXCEPT + SELECT 'Extra'[?],* FROM #Before + ) + UNION ALL + ( + SELECT 'Missing'[?],* FROM #Before + EXCEPT + SELECT 'Missing'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + ) + ) X; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTableTests.[test if @TableAction is Hide, @Action Save, Save, Reset, Reset: does nothing in total] +AS +BEGIN + CREATE TABLE Private_NoTransactionHandleTableTests.Table1 (Id INT, col1 NVARCHAR(MAX)); + INSERT INTO Private_NoTransactionHandleTableTests.Table1 VALUES (2,'c'), (3,'a'); + + SELECT object_id, SCHEMA_NAME(schema_id) [schema_name], name INTO #Before FROM sys.tables; + + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Hide'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Save', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Hide'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Hide'; + EXEC tSQLt.Private_NoTransactionHandleTable @Action = 'Reset', @FullTableName = '[Private_NoTransactionHandleTableTests].[Table1]', @TableAction = 'Hide'; + EXEC tSQLt.UndoTestDoubles; + + SELECT * INTO #Actual + FROM ( + ( + SELECT 'Extra'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + EXCEPT + SELECT 'Extra'[?],* FROM #Before + ) + UNION ALL + ( + SELECT 'Missing'[?],* FROM #Before + EXCEPT + SELECT 'Missing'[?],object_id, SCHEMA_NAME(schema_id) [schema_name], name FROM sys.tables + ) + ) X; + + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + diff --git a/Tests/Private_NoTransactionHandleTablesTests.class.sql b/Tests/Private_NoTransactionHandleTablesTests.class.sql new file mode 100644 index 000000000..977e8b348 --- /dev/null +++ b/Tests/Private_NoTransactionHandleTablesTests.class.sql @@ -0,0 +1,152 @@ +EXEC tSQLt.NewTestClass 'Private_NoTransactionHandleTablesTests'; +GO +CREATE PROCEDURE Private_NoTransactionHandleTablesTests.[test does not call tSQLt.Private_NoTransactionHandleTable if tSQLt.Private_NoTransactionTableAction is empty] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTable'; + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NoTransactionTableAction'; + + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + + EXEC tSQLt.AssertEmptyTable @TableName = 'tSQLt.Private_NoTransactionHandleTable_SpyProcedureLog'; + +END; +GO +CREATE PROCEDURE Private_NoTransactionHandleTablesTests.[test calls tSQLt.Private_NoTransactionHandleTable if tSQLt.Private_NoTransactionTableAction contains a table] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTable'; + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NoTransactionTableAction'; + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl1'',''Restore'');') + + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + + SELECT FullTableName INTO #Actual FROM tSQLt.Private_NoTransactionHandleTable_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('tbl1'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_NoTransactionHandleTablesTests.[test calls tSQLt.Private_NoTransactionHandleTable for each table in tSQLt.Private_NoTransactionTableAction] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTable'; + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NoTransactionTableAction'; + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl1'',''Restore'');') + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl2'',''Restore'');') + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl3'',''Restore'');') + + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + + SELECT FullTableName INTO #Actual FROM tSQLt.Private_NoTransactionHandleTable_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('tbl1'),('tbl2'),('tbl3'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_NoTransactionHandleTablesTests.[test does not call tSQLt.Private_NoTransactionHandleTable for 'Ignore' tables] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTable'; + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NoTransactionTableAction'; + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl1'',''Restore'');') + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl2'',''Ignore'');') + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl3'',''Restore'');') + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl4'',''Other'');') + + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + + SELECT FullTableName INTO #Actual FROM tSQLt.Private_NoTransactionHandleTable_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('tbl1'),('tbl3'),('tbl4'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_NoTransactionHandleTablesTests.[test passes @Action to tSQLt.Private_NoTransactionHandleTable for each table] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTable'; + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NoTransactionTableAction'; + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl1'',''Restore'');') + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl2'',''Restore'');') + EXEC('INSERT INTO tSQLt.Private_NoTransactionTableAction VALUES(''tbl3'',''Restore'');') + + EXEC tSQLt.Private_NoTransactionHandleTables @Action='A Specific Action'; + + SELECT FullTableName, Action INTO #Actual FROM tSQLt.Private_NoTransactionHandleTable_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('tbl1', 'A Specific Action'),('tbl2', 'A Specific Action'),('tbl3', 'A Specific Action'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_NoTransactionHandleTablesTests.[test is rerunnable (though it still needs some help from UndoTestDoubles)] +AS +BEGIN + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Save'; + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Private_NoTransactionHandleTablesTests.[test combination of Private_NoTransactionHandleTables and UndoTestDoubles is rerunnable] +AS +BEGIN + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Save'; + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + EXEC tSQLt.UndoTestDoubles; + EXEC tSQLt.Private_NoTransactionHandleTables @Action='Reset'; + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO diff --git a/Tests/Private_NoTransactionTableActionTests.class.sql b/Tests/Private_NoTransactionTableActionTests.class.sql new file mode 100644 index 000000000..35850d49a --- /dev/null +++ b/Tests/Private_NoTransactionTableActionTests.class.sql @@ -0,0 +1,33 @@ +EXEC tSQLt.NewTestClass 'Private_NoTransactionTableActionTests'; +GO +CREATE PROCEDURE Private_NoTransactionTableActionTests.[test contains all tSQLt tables] +AS +BEGIN + SELECT Name INTO #Actual FROM tSQLt.Private_NoTransactionTableAction; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + SELECT QUOTENAME(SCHEMA_NAME(t.schema_id))+'.'+QUOTENAME(t.name) + FROM sys.tables t WHERE schema_id = SCHEMA_ID('tSQLt') AND name NOT LIKE ('%SpyProcedureLog'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +CREATE PROCEDURE Private_NoTransactionTableActionTests.[test has the correct actions for all tSQLt tables] +AS +BEGIN + SELECT Name, Action INTO #Actual FROM tSQLt.Private_NoTransactionTableAction; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + SELECT '[tSQLt].[Private_NewTestClassList]','Hide' UNION ALL + SELECT '[tSQLt].[Run_LastExecution]', 'Hide' UNION ALL + SELECT '[tSQLt].[Private_Configurations]', 'Restore' UNION ALL + SELECT '[tSQLt].[CaptureOutputLog]', 'Truncate' UNION ALL + SELECT '[tSQLt].[Private_RenamedObjectLog]','Ignore' UNION ALL + SELECT '[tSQLt].[Private_Seize]','Ignore' UNION ALL + SELECT '[tSQLt].[Private_Seize_NoTruncate]','Ignore' UNION ALL + SELECT '[tSQLt].[TestResult]', 'Restore'; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO \ No newline at end of file diff --git a/Tests/Private_NullCellTableTests.class.sql b/Tests/Private_NullCellTableTests.class.sql deleted file mode 100644 index e04a125ac..000000000 --- a/Tests/Private_NullCellTableTests.class.sql +++ /dev/null @@ -1,131 +0,0 @@ - EXEC tSQLt.NewTestClass 'Private_NullCellTableTests'; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[test table contains a single null cell] - AS - BEGIN - CREATE TABLE Private_NullCellTableTests.Expected (I INT); - INSERT INTO Private_NullCellTableTests.Expected(I) VALUES (NULL); - - EXEC tSQLt.AssertEqualsTable 'Private_NullCellTableTests.Expected', 'tSQLt.Private_NullCellTable'; - END; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[AssertStatementPerformsNoDataChangeToTable] - @Statement NVARCHAR(MAX) - AS - BEGIN - CREATE TABLE Private_NullCellTableTests.Expected (I INT); - INSERT INTO Private_NullCellTableTests.Expected(I) VALUES (NULL); - - BEGIN TRY - EXEC @Statement; - END TRY - BEGIN CATCH - -- Left intentionally empty - END CATCH; - - EXEC tSQLt.AssertEqualsTable 'Private_NullCellTableTests.Expected', 'tSQLt.Private_NullCellTable'; - - END; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[test cannot insert second NULL row] - AS - BEGIN - EXEC Private_NullCellTableTests.[AssertStatementPerformsNoDataChangeToTable] 'INSERT INTO tSQLt.Private_NullCellTable (I) VALUES (NULL);'; - END; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[test cannot insert a non-NULL row] - AS - BEGIN - EXEC Private_NullCellTableTests.[AssertStatementPerformsNoDataChangeToTable] 'INSERT INTO tSQLt.Private_NullCellTable (I) VALUES (5);'; - END; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[test cannot delete row] - AS - BEGIN - EXEC Private_NullCellTableTests.[AssertStatementPerformsNoDataChangeToTable] 'DELETE FROM tSQLt.Private_NullCellTable;'; - END; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[test cannot update row] - AS - BEGIN - EXEC Private_NullCellTableTests.[AssertStatementPerformsNoDataChangeToTable] 'UPDATE tSQLt.Private_NullCellTable SET I = 13;'; - END; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[test can insert a row if the table is empty] - AS - BEGIN - EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NullCellTable'; - EXEC tSQLt.ApplyTrigger @TableName = 'tSQLt.Private_NullCellTable', @TriggerName= 'tSQLt.Private_NullCellTable_StopModifications'; - - INSERT INTO tSQLt.Private_NullCellTable VALUES (NULL); - - SELECT * INTO #Actual FROM tSQLt.Private_NullCellTable; - - SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; - - INSERT INTO #Expected VALUES (NULL); - - EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; - END; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[test any insert will insert NULL row if table is empty] - AS - BEGIN - EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NullCellTable'; - EXEC tSQLt.ApplyTrigger @TableName = 'tSQLt.Private_NullCellTable', @TriggerName= 'tSQLt.Private_NullCellTable_StopModifications'; - - INSERT INTO tSQLt.Private_NullCellTable VALUES (10),(11),(12); - - SELECT * INTO #Actual FROM tSQLt.Private_NullCellTable; - - SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; - - INSERT INTO #Expected VALUES (NULL); - - EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; - END; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[test any update will insert NULL row if table is empty] - AS - BEGIN - EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NullCellTable'; - EXEC tSQLt.ApplyTrigger @TableName = 'tSQLt.Private_NullCellTable', @TriggerName= 'tSQLt.Private_NullCellTable_StopModifications'; - - UPDATE tSQLt.Private_NullCellTable SET I = I WHERE 1=0; - - SELECT * INTO #Actual FROM tSQLt.Private_NullCellTable; - - SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; - - INSERT INTO #Expected VALUES (NULL); - - EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; - END; - GO - - CREATE PROCEDURE Private_NullCellTableTests.[test any delete will insert NULL row if table is empty] - AS - BEGIN - EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NullCellTable'; - EXEC tSQLt.ApplyTrigger @TableName = 'tSQLt.Private_NullCellTable', @TriggerName= 'tSQLt.Private_NullCellTable_StopModifications'; - - DELETE tSQLt.Private_NullCellTable WHERE 1=0; - - SELECT * INTO #Actual FROM tSQLt.Private_NullCellTable; - - SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; - - INSERT INTO #Expected VALUES (NULL); - - EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; - END; - GO diff --git a/tests/Private_RemoveSchemaBoundReferencesTests.class.sql b/Tests/Private_RemoveSchemaBoundReferencesTests.class.sql similarity index 100% rename from tests/Private_RemoveSchemaBoundReferencesTests.class.sql rename to Tests/Private_RemoveSchemaBoundReferencesTests.class.sql diff --git a/Tests/Private_ResultsTests.class.sql b/Tests/Private_ResultsTests.class.sql new file mode 100644 index 000000000..26460f1a6 --- /dev/null +++ b/Tests/Private_ResultsTests.class.sql @@ -0,0 +1,19 @@ +EXEC tSQLt.NewTestClass 'Private_ResultsTests'; +GO +CREATE PROCEDURE Private_ResultsTests.[test Contains all Result values (double ledger)] +AS +BEGIN + SELECT Severity, Result INTO #Actual FROM tSQLt.Private_Results; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + SELECT 1,'Success' UNION ALL + SELECT 2,'Skipped' UNION ALL + SELECT 3,'Failure' UNION ALL + SELECT 4,'Error' UNION ALL + SELECT 5,'Abort' UNION ALL + SELECT 6,'FATAL' ; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO diff --git a/Tests/Private_SeizeTests.class.sql b/Tests/Private_SeizeTests.class.sql new file mode 100644 index 000000000..ae0f18982 --- /dev/null +++ b/Tests/Private_SeizeTests.class.sql @@ -0,0 +1,59 @@ +EXEC tSQLt.NewTestClass 'Private_SeizeTests'; +GO +CREATE PROCEDURE Private_SeizeTests.[test can insert a 1] +AS +BEGIN + INSERT INTO tSQLt.Private_Seize(Kaput) VALUES(1); + + SELECT Kaput INTO #Actual FROM tSQLt.Private_Seize; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +CREATE PROCEDURE Private_SeizeTests.[test cannot insert <> 1] +AS +BEGIN + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%"Private_Seize:CHK"%'; + INSERT INTO tSQLt.Private_Seize(Kaput) VALUES(0); + +END; +GO +CREATE PROCEDURE Private_SeizeTests.[test cannot insert NULL] +AS +BEGIN + EXEC tSQLt.ExpectException @ExpectedMessagePattern = '%column does not allow nulls%'; + INSERT INTO tSQLt.Private_Seize(Kaput) VALUES(NULL); + +END; +GO +CREATE PROCEDURE Private_SeizeTests.[test cannot delete row] +AS +BEGIN + INSERT INTO tSQLt.Private_Seize(Kaput) VALUES(1); + + EXEC tSQLt.ExpectException @ExpectedMessage = 'This is a private table that you should not mess with!', @ExpectedSeverity = 16, @ExpectedState = 10; + DELETE FROM tSQLt.Private_Seize; +END; +GO +CREATE PROCEDURE Private_SeizeTests.[test cannot update row (even to the same value)] +AS +BEGIN + INSERT INTO tSQLt.Private_Seize(Kaput) VALUES(1); + + EXEC tSQLt.ExpectException @ExpectedMessage = 'This is a private table that you should not mess with!', @ExpectedSeverity = 16, @ExpectedState = 10; + UPDATE tSQLt.Private_Seize SET Kaput = 1; +END; +GO +CREATE PROCEDURE Private_SeizeTests.[test cannot TRUNCATE] +AS +BEGIN + INSERT INTO tSQLt.Private_Seize(Kaput) VALUES(1); + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = 'Cannot truncate table _tSQLt.Private_Seize_%'; + TRUNCATE TABLE tSQLt.Private_Seize; +END; +GO diff --git a/Tests/Run_Methods_Tests.class.sql b/Tests/Run_Methods_Tests.class.sql index 8022d5b06..cab6f411a 100644 --- a/Tests/Run_Methods_Tests.class.sql +++ b/Tests/Run_Methods_Tests.class.sql @@ -81,27 +81,35 @@ GO CREATE PROC Run_Methods_Tests.test_Run_handles_test_names_with_spaces AS BEGIN - DECLARE @ProductMajorVersion INT; - EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; + DECLARE @ProductMajorVersion INT; + EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; - EXEC('CREATE SCHEMA MyTestClass;'); - EXEC('CREATE PROC MyTestClass.[Test Case A] AS RAISERROR(''GotHere'',16,10);'); + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE PROC MyTestClass.[Test Case A] AS RAISERROR(''<><><> GotHere <><><>'',16,10);'); - BEGIN TRY - EXEC tSQLt.Run 'MyTestClass.Test Case A'; - END TRY - BEGIN CATCH - --This space left intentionally blank - END CATCH - SELECT Class, TestCase, Msg - INTO Run_Methods_Tests.actual - FROM tSQLt.TestResult; - SELECT TOP(0)* INTO Run_Methods_Tests.expected FROM Run_Methods_Tests.actual; + BEGIN TRY + EXEC tSQLt.Run 'MyTestClass.Test Case A'; + END TRY + BEGIN CATCH + --This space left intentionally blank + END CATCH + SELECT Class, TestCase, Msg + INTO #Actual + FROM tSQLt.TestResult; + + SELECT TOP(0) A.Class,A.TestCase,A.Msg AS [%Msg] INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + SELECT 'MyTestClass' Class, 'Test Case A' TestCase, '%<><><> GotHere <><><>%' Msg - INSERT INTO Run_Methods_Tests.expected - SELECT 'MyTestClass' Class, 'Test Case A' TestCase, 'GotHere[16,10]{'+CASE WHEN @ProductMajorVersion >= 14 THEN 'MyTestClass.' ELSE '' END+'Test Case A,1}' Msg + SELECT * INTO #Compare + FROM( + SELECT '>' _R_,* FROM #Actual AS A WHERE NOT EXISTS(SELECT 1 FROM #Expected E WHERE A.Class = E.Class AND A.TestCase = E.TestCase AND A.Msg LIKE E.[%Msg]) + UNION ALL + SELECT '<' _R_,* FROM #Expected AS E WHERE NOT EXISTS(SELECT 1 FROM #Actual A WHERE A.Class = E.Class AND A.TestCase = E.TestCase AND A.Msg LIKE E.[%Msg]) + )X - EXEC tSQLt.AssertEqualsTable 'Run_Methods_Tests.expected', 'Run_Methods_Tests.actual'; + EXEC tSQLt.AssertEmptyTable '#Compare'; END; GO @@ -1450,29 +1458,14 @@ BEGIN EXEC tSQLt.NewTestClass 'Test Class B'; EXEC tSQLt.NewTestClass 'Test Class C'; - DECLARE @TestClassCursor CURSOR; - EXEC tSQLt.Private_GetCursorForRunNew @TestClassCursor = @TestClassCursor OUT; - - SELECT Class - INTO #Actual - FROM tSQLt.TestResult - WHERE 1=0; - - DECLARE @TestClass NVARCHAR(MAX); - WHILE(1=1) - BEGIN - FETCH NEXT FROM @TestClassCursor INTO @TestClass; - IF(@@FETCH_STATUS<>0)BREAK; - INSERT INTO #Actual VALUES(@TestClass); - END; - CLOSE @TestClassCursor; - DEALLOCATE @TestClassCursor; + CREATE TABLE #TestClassesForRunCursor(Name NVARCHAR(MAX)); + EXEC tSQLt.Private_GetCursorForRunNew; - SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + SELECT TOP(0) A.* INTO #Expected FROM #TestClassesForRunCursor A RIGHT JOIN #TestClassesForRunCursor X ON 1=0; INSERT INTO #Expected VALUES('Test Class B'); INSERT INTO #Expected VALUES('Test Class C'); - EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + EXEC tSQLt.AssertEqualsTable '#Expected','#TestClassesForRunCursor'; END; GO @@ -1486,29 +1479,13 @@ BEGIN EXEC tSQLt.NewTestClass 'Test Class C'; EXEC tSQLt.DropClass 'Test Class C'; - DECLARE @TestClassCursor CURSOR; - EXEC tSQLt.Private_GetCursorForRunNew @TestClassCursor = @TestClassCursor OUT; - - SELECT Class - INTO #Actual - FROM tSQLt.TestResult - WHERE 1=0; - - DECLARE @TestClass NVARCHAR(MAX); - WHILE(1=1) - BEGIN - FETCH NEXT FROM @TestClassCursor INTO @TestClass; - IF(@@FETCH_STATUS<>0)BREAK; - INSERT INTO #Actual VALUES(@TestClass); - END; - CLOSE @TestClassCursor; - DEALLOCATE @TestClassCursor; + CREATE TABLE #TestClassesForRunCursor(Name NVARCHAR(MAX)); + EXEC tSQLt.Private_GetCursorForRunNew; - SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + SELECT TOP(0) A.* INTO #Expected FROM #TestClassesForRunCursor A RIGHT JOIN #TestClassesForRunCursor X ON 1=0; INSERT INTO #Expected VALUES('Test Class B'); - EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; - + EXEC tSQLt.AssertEqualsTable '#Expected','#TestClassesForRunCursor'; END; GO CREATE PROC Run_Methods_Tests.[test Privat_RunNew calls Private_RunCursor with correct cursor] @@ -2243,3 +2220,284 @@ BEGIN END; END GO +CREATE PROCEDURE Run_Methods_Tests.[test tSQLt.RunAll can handle classes with single quotes] +AS +BEGIN + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_RunTestClass'; + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestClasses'; + EXEC('INSERT INTO tSQLt.TestClasses VALUES(''a class with a '''' in the middle'',12321);'); + + EXEC tSQLt.RunAll; + + SELECT TestClassName + INTO #Actual + FROM tSQLt.Private_RunTestClass_SpyProcedureLog; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES ('a class with a '' in the middle'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/* ----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test tSQLt.RunAll executes tests with single quotes in class and name] +AS +BEGIN + CREATE TABLE #Actual (Id INT); + EXEC ('CREATE SCHEMA [a class with a '' in the middle];'); + EXEC (' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [a class with a '' in the middle].[test with a '' in the middle] AS BEGIN INSERT INTO #Actual VALUES (1); END; + '); + + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestClasses'; + EXEC('INSERT INTO tSQLt.TestClasses VALUES(''a class with a '''' in the middle'',12321);'); + + EXEC tSQLt.SetSummaryError 0; + + EXEC tSQLt.RunAll; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES (1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/* ----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test tSQLt.RunTestClass executes tests with single quotes in class and name] +AS +BEGIN + CREATE TABLE #Actual (Id INT); + EXEC ('CREATE SCHEMA [a class with a '' in the middle];'); + EXEC (' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [a class with a '' in the middle].[test with a '' in the middle] AS BEGIN INSERT INTO #Actual VALUES (1); END; + '); + + EXEC tSQLt.RunTestClass @TestClassName = '[a class with a '' in the middle]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES (1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/* ----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test tSQLt.Run executes test class with single quotes in name] +AS +BEGIN + CREATE TABLE #Actual (Id INT); + EXEC ('CREATE SCHEMA [a class with a '' in the middle];'); + EXEC (' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [a class with a '' in the middle].[test with a '' in the middle] AS BEGIN INSERT INTO #Actual VALUES (1); END; + '); + + EXEC tSQLt.Run '[a class with a '' in the middle]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES (1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/* ----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test tSQLt.Run executes test with single quotes in class and test names] +AS +BEGIN + CREATE TABLE #Actual (Id INT); + EXEC ('CREATE SCHEMA [a class with a '' in the middle];'); + EXEC (' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [a class with a '' in the middle].[test with a '' in the middle] AS BEGIN INSERT INTO #Actual VALUES (1); END; + '); + + EXEC tSQLt.Run '[a class with a '' in the middle].[test with a '' in the middle]'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES (1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/* ----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test tSQLt.RunNew executes test class with single quotes in class and test names] +AS +BEGIN + CREATE TABLE #Actual (Id INT); + EXEC ('CREATE SCHEMA [a class with a '' in the middle];'); + EXEC (' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [a class with a '' in the middle].[test with a '' in the middle] AS BEGIN INSERT INTO #Actual VALUES (1); END; + '); + EXEC tSQLt.FakeTable @TableName = 'tSQLt.TestClasses'; + + EXEC tSQLt.FakeTable @TableName = 'tSQLt.Private_NewTestClassList'; + + EXEC('INSERT INTO tSQLt.TestClasses(Name) VALUES(''a class with a '''' in the middle'');'); + EXEC('INSERT INTO tSQLt.Private_NewTestClassList(ClassName) VALUES(''a class with a '''' in the middle'');'); + + EXEC tSQLt.SetSummaryError 0; + + EXEC tSQLt.RunNew; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES (1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test FATAL error prevents subsequent tSQLt.Run% calls] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables', @CommandToExecute = 'IF(@Action = ''Reset'')BEGIN RAISERROR(''Some Fatal Error'',16,10);END;'; + + BEGIN TRY + EXEC tSQLt.Run 'MyInnerTests', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + END TRY + BEGIN CATCH + /* not interested in this error */ + END CATCH; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'tSQLt is in an invalid state. Please reinstall tSQLt.'; + EXEC tSQLt.Run 'MyInnerTests', @TestResultFormatter = 'tSQLt.NullTestResultFormatter'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test when @Result=FATAL an appropriate error message is raised] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables', @CommandToExecute = 'IF(@Action = ''Reset'')BEGIN RAISERROR(''Some Fatal Error'',16,10);END;'; + + EXEC tSQLt.SetSummaryError @SummaryError = 1; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'The last test has invalidated the current installation of tSQLt. Please reinstall tSQLt.'; + EXEC tSQLt.Run 'MyInnerTests'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE Run_Methods_Tests.[test when @Result=Abort an appropriate error message is raised] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTests' + EXEC(' + --[@'+'tSQLt:NoTransaction](DEFAULT) + CREATE PROCEDURE [MyInnerTests].[test1] + AS + BEGIN + RETURN; + END; + '); + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_AssertNoSideEffects'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.UndoTestDoubles', @CommandToExecute = 'RAISERROR(''Some Fatal Error'',16,10);'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_NoTransactionHandleTables'; + + EXEC tSQLt.SetSummaryError @SummaryError = 1; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'Aborting the current execution of tSQLt due to a severe error.'; + EXEC tSQLt.Run 'MyInnerTests'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +--[@tSQLt:SkipTest]('TODO: tSQLt should handle this, but does not at the moment because of the issue with transactions started within a try-catch block') +CREATE PROCEDURE Run_Methods_Tests.[test produces meaningful error when pre and post transactions counts don't match] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTestsA' + EXEC('CREATE PROCEDURE MyInnerTestsA.[test should execute outside of transaction] AS BEGIN TRAN;'); + + EXEC tSQLt.ExpectException @ExpectedMessage = 'SOMETHING RATHER', @ExpectedSeverity = NULL, @ExpectedState = NULL; + EXEC tSQLt.Run 'MyInnerTestsA.[test should execute outside of transaction]'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +--[@tSQLt:SkipTest]('TODO: tSQLt should handle this, but does not at the moment because of the issue with transactions started within a try-catch block') +CREATE PROCEDURE Run_Methods_Tests.[test produces meaningful error when pre and post transactions counts don't match in NoTransaction test] +AS +BEGIN + EXEC tSQLt.NewTestClass 'MyInnerTestsB' + EXEC(' +--[@'+'tSQLt:NoTransaction](DEFAULT) +CREATE PROCEDURE MyInnerTestsB.[test should execute outside of transaction] AS BEGIN TRAN; + '); + + EXEC tSQLt.ExpectException @ExpectedMessage = 'SOMETHING RATHER', @ExpectedSeverity = NULL, @ExpectedState = NULL; + EXEC tSQLt.Run 'MyInnerTestsB.[test should execute outside of transaction]'; + + -- FOR FUTURE DEBUGGING + --BEGIN TRY + -- EXEC tSQLt.Run 'MyInnerTestsB.[test should execute outside of transaction]'; + --END TRY + --BEGIN CATCH + -- SELECT * FROM fn_dblog(NULL,NULL) WHERE [Transaction ID] = (SELECT LL.[Transaction ID] FROM fn_dblog(NULL,NULL) LL JOIN sys.dm_tran_current_transaction AS DTCT ON DTCT.transaction_id = LL.[Xact ID]); + --END CATCH; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +/*-- + Transaction Tests to be considered + + 1. NoTransaction test starts a transaction + 1a. The transaction is commitable (THIS FEELS UNLIKELY GIVEN OUR RESEARCH.) + 1b. The transaction is not commitable + + 2. Transaction test rolls-back tSQLt transaction(s) + 2a. No transaction after test (ROLLBACK, but no new transactions created within the test) + 2b. New transaction after test (ROLLBACK, but new one created within the test) + + 3. Transaction test commits tSQLt transactions(s) + 3a. No transaction after test (COMMIT, but no new transactions created within the test) + 3b. New transaction after test (COMMIT, but new one created within the test) + + 4. Transaction test renders transaction uncommitable (Won't be able to write to anything, including tSQLt.TestResults or a variety of temp tables.) + + - currently AssertNoSideEffects causes additional problems if executed inside an uncommittable transaction (It tries to write to a new temp table) <-- does this need to be changed? + - do existing tests already cover some of the scenarios described above? + +--*/ diff --git a/Tests/SpyProcedureTests.class.sql b/Tests/SpyProcedureTests.class.sql index 12d830d50..d3e138cc5 100644 --- a/Tests/SpyProcedureTests.class.sql +++ b/Tests/SpyProcedureTests.class.sql @@ -351,6 +351,30 @@ BEGIN END; GO +CREATE PROC SpyProcedureTests.[test SpyProcedure handles procedure and schema names with single quotes] +AS +BEGIN + DECLARE @ErrorRaised INT; SET @ErrorRaised = 0; + + EXEC('CREATE SCHEMA [Tes''tSchema];'); + EXEC('CREATE PROC [Tes''tSchema].[Spye''e Proc] @testParam1 INT AS RETURN 0;'); + + EXEC tSQLt.SpyProcedure '[Tes''tSchema].[Spye''e Proc]'; + + DECLARE @InnerProcedure VARCHAR(MAX) = '[Tes''tSchema].[Spye''e Proc]'; + EXEC @InnerProcedure 27; + + SELECT testParam1 + INTO #Actual + FROM [Tes'tSchema].[Spye'e Proc_SpyProcedureLog]; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(27); + + EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual'; +END; +GO + CREATE PROC SpyProcedureTests.[test SpyProcedure calls tSQLt.Private_RenameObjectToUniqueName on original proc] AS BEGIN @@ -682,9 +706,10 @@ BEGIN EXEC tSQLt.Private_GenerateCreateProcedureSpyStatement @ProcedureObjectId = @ProcedureObjectId, - @OriginalProcedureName = 'dbo.SpiedInnerProcedure', /*using different name to simulate renaming*/ + @OriginalProcedureName = 'dbo.OriginalInnerProcedure', @LogTableName = NULL, @CommandToExecute = NULL, + @CallOriginal = 0, @CreateProcedureStatement = @CreateProcedureStatement OUT, @CreateLogTableStatement = @CreateLogTableStatement OUT; @@ -695,7 +720,7 @@ GO CREATE PROC SpyProcedureTests.[test Private_CreateProcedureSpy does create spy when @LogTableName is NULL] AS BEGIN - EXEC('CREATE PROC dbo.OriginalInnerProcedure AS RETURN;'); + EXEC('CREATE PROC dbo.OriginalInnerProcedure AS RETURN;'); /* This is the simulated rename of dbo.SpiedInnerProcedure */ DECLARE @ProcedureObjectId INT = OBJECT_ID('dbo.OriginalInnerProcedure'); @@ -704,9 +729,10 @@ BEGIN EXEC tSQLt.Private_GenerateCreateProcedureSpyStatement @ProcedureObjectId = @ProcedureObjectId, - @OriginalProcedureName = 'dbo.SpiedInnerProcedure', /*using different name to simulate renaming*/ + @OriginalProcedureName = 'dbo.SpiedInnerProcedure', @LogTableName = NULL, @CommandToExecute = NULL, + @CallOriginal = 0, @CreateProcedureStatement = @CreateProcedureStatement OUT, @CreateLogTableStatement = @CreateLogTableStatement OUT; @@ -723,7 +749,8 @@ BEGIN EXEC tSQLt.SpyProcedure @ProcedureName = 'dbo.InnerProcedure'; - EXEC dbo.InnerProcedure @expectedCommand = 'Select 1 [Int]', @actualCommand = 'Select ''c'' [char]'; + DECLARE @ProcName NVARCHAR(MAX) = 'dbo.InnerProcedure'; + EXEC @ProcName @expectedCommand = 'Select 1 [Int]', @actualCommand = 'Select ''c'' [char]'; SELECT expectedCommand, actualCommand INTO #Actual FROM dbo.InnerProcedure_SpyProcedureLog; SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; @@ -755,12 +782,12 @@ BEGIN EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO -CREATE PROC SpyProcedureTests.[test SpyProcedure calls tSQLt.Private_MarktSQLtTempObject on new object] +CREATE PROC SpyProcedureTests.[test SpyProcedure calls tSQLt.Private_MarktSQLtTempObject on new objects] AS BEGIN DECLARE @OriginalObjectId INT = OBJECT_ID('tSQLt.Private_MarktSQLtTempObject'); - EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_MarktSQLtTempObject'; + EXEC tSQLt.SpyProcedure @ProcedureName = '[tSQLt].[Private_MarktSQLtTempObject]'; --We are testing that SpyProcedure calls this ^^ after spying this ^^, so this line serves as prep and action. SELECT ObjectName, ObjectType, NewNameOfOriginalObject @@ -769,11 +796,355 @@ BEGIN SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; INSERT INTO #Expected - VALUES('tSQLt.Private_MarktSQLtTempObject', N'PROCEDURE', OBJECT_NAME(@OriginalObjectId)); + VALUES('[tSQLt].[Private_MarktSQLtTempObject]', N'PROCEDURE', OBJECT_NAME(@OriginalObjectId)), + ('[tSQLt].[Private_MarktSQLtTempObject_SpyProcedureLog]', N'TABLE', NULL); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test new SpyProcedureLog table is marked as tSQLt.IsTempObject] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS RETURN;'); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1'; + + SELECT name, value + INTO #Actual + FROM sys.extended_properties + WHERE class_desc = 'OBJECT_OR_COLUMN' + AND major_id = OBJECT_ID('SpyProcedureTests.TempProcedure1_SpyProcedureLog') + AND name = 'tSQLt.IsTempObject'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('tSQLt.IsTempObject', 1); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test can handle existing SpyProcedureLog table] +AS +BEGIN + CREATE TABLE SpyProcedureTests.TempProcedure1_SpyProcedureLog ([Please don't do this] INT, [but just in case, we can handle it] NVARCHAR(MAX)); + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS RETURN;'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test calls the original procedure if @CallOriginal = 1] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1 called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CallOriginal = 1; + + EXEC('SpyProcedureTests.TempProcedure1'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'SpyProcedureTests.TempProcedure1 called'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test does not call the original procedure if @CallOriginal = 0] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1 called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CallOriginal = 0; + + EXEC('SpyProcedureTests.TempProcedure1'); + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test does not call the original procedure if @CallOriginal = NULL] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1 called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CallOriginal = NULL; + + EXEC('SpyProcedureTests.TempProcedure1'); + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test does not call the original procedure if @CallOriginal is not specified] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1 called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1'; + + EXEC('SpyProcedureTests.TempProcedure1'); + + EXEC tSQLt.AssertEmptyTable '#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test calls the original procedure if @CallOriginal = 1 even if schema or procedure name require quoting] +AS +BEGIN + EXEC('CREATE SCHEMA [Inner Test Schema];'); + EXEC('CREATE PROCEDURE [Inner Test Schema].[Temp Procedure 1] AS BEGIN INSERT INTO #Actual VALUES (''[Inner Test Schema].[Temp Procedure 1] called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = '[Inner Test Schema].[Temp Procedure 1]', @CallOriginal = 1; + + EXEC('[Inner Test Schema].[Temp Procedure 1]'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'[Inner Test Schema].[Temp Procedure 1] called'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +CREATE PROC SpyProcedureTests.[test calls original procedure with parameters if @CallOriginal = 1] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 @AnInt INT, @AString NVARCHAR(MAX) AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1(''+CAST(@AnInt AS NVARCHAR(MAX))+'',''+@AString+'') called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CallOriginal = 1; + + EXEC('EXEC SpyProcedureTests.TempProcedure1 42,''XYZ'';'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'SpyProcedureTests.TempProcedure1(42,XYZ) called'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test calls original procedure with OUTPUT parameters if @CallOriginal = 1] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 @AnInt INT OUTPUT, @AString NVARCHAR(MAX) OUTPUT AS BEGIN SELECT @AnInt = 8383, @AString = ''424242''; END;'); + + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CallOriginal = 1; + + DECLARE @InputOutputInt INT = -17; + DECLARE @InputOutputString NVARCHAR(MAX) = '007'; + DECLARE @ProcedureNameVariableSoWeDoNotGetAWarning NVARCHAR(MAX) = 'SpyProcedureTests.TempProcedure1'; + EXEC @ProcedureNameVariableSoWeDoNotGetAWarning @InputOutputInt OUT, @InputOutputString OUT; + + SELECT @InputOutputInt AS AnInt, @InputOutputString AS AString INTO #Actual; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(8383, '424242'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test calls original procedure with cursor parameters if @CallOriginal = 1] +AS +BEGIN + EXEC(' + CREATE PROCEDURE SpyProcedureTests.TempProcedure1 @Cursor1 CURSOR VARYING OUTPUT, @NotACursor INT, @Cursor2 CURSOR VARYING OUTPUT + AS + BEGIN + DECLARE @FirstInt INT; + DECLARE @SecondInt INT; + OPEN @Cursor1; + FETCH NEXT FROM @Cursor1 INTO @FirstInt; + CLOSE @Cursor1; + DEALLOCATE @Cursor1; + OPEN @Cursor2; + FETCH NEXT FROM @Cursor2 INTO @SecondInt; + CLOSE @Cursor2; + DEALLOCATE @Cursor2; + INSERT INTO #Actual VALUES(@FirstInt, @SecondInt, @NotACursor); + END;' + ); + CREATE TABLE #Actual (intA INT, intB INT, intC INT); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CallOriginal = 1; + + DECLARE @InputOnlyInt INT = 17; + DECLARE @Cursor11 CURSOR; SET @Cursor11 = CURSOR FOR SELECT 36; + DECLARE @Cursor22 CURSOR; SET @Cursor22 = CURSOR FOR SELECT 42; + DECLARE @ProcedureNameVariableSoWeDoNotGetAWarning NVARCHAR(MAX) = 'SpyProcedureTests.TempProcedure1'; + + EXEC @ProcedureNameVariableSoWeDoNotGetAWarning @Cursor11, @InputOnlyInt, @Cursor22; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(36, 42, 17); EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE TYPE SpyProcedureTests.Type1 AS TABLE(A INT); +GO +CREATE PROC SpyProcedureTests.[test calls original procedure with table valued parameters if @CallOriginal = 1] +AS +BEGIN + EXEC(' + CREATE PROCEDURE SpyProcedureTests.TempProcedure1 @Table1 SpyProcedureTests.Type1 READONLY, @NotATable INT, @Table2 SpyProcedureTests.Type1 READONLY + AS + BEGIN + DECLARE @FirstInt INT = (SELECT A FROM @Table1); + DECLARE @SecondInt INT = (SELECT A FROM @Table2); + INSERT INTO #Actual VALUES(@FirstInt, @SecondInt, @NotATable); + END;' + ); + CREATE TABLE #Actual (intA INT, intB INT, intC INT); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CallOriginal = 1; + + EXEC(' + DECLARE @InputOnlyInt INT = 17; + DECLARE @Table11 AS SpyProcedureTests.Type1; INSERT INTO @Table11 VALUES(36); + DECLARE @Table22 AS SpyProcedureTests.Type1; INSERT INTO @Table22 VALUES(42); + DECLARE @ProcedureNameVariableSoWeDoNotGetAWarning NVARCHAR(MAX) = ''SpyProcedureTests.TempProcedure1''; + EXEC @ProcedureNameVariableSoWeDoNotGetAWarning @Table11, @InputOnlyInt, @Table22; + '); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(36, 42, 17); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test calls the original procedure after @CommandToExecute if @CallOriginal = 1] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1 called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CommandToExecute='INSERT INTO #Actual VALUES (''CommandToExecute called'');', @CallOriginal = 1; + + EXEC('SpyProcedureTests.TempProcedure1'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'CommandToExecute called'),(2,'SpyProcedureTests.TempProcedure1 called'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test @SpyProcedureOriginalObjectName contains original proc name inside spy] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 @I INT = NULL AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1 called'', @I); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX), I INT); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CommandToExecute='EXEC @SpyProcedureOriginalObjectName 42;'; + + EXEC('SpyProcedureTests.TempProcedure1'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'SpyProcedureTests.TempProcedure1 called',42); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test @SpyProcedureOriginalObjectName contains original proc name inside spy even if quoting is required] +AS +BEGIN + EXEC('CREATE SCHEMA [Inner Test.Schema 1];'); + EXEC('CREATE PROCEDURE [Inner Test.Schema 1].[Temp Proc.edure 1] @I INT = NULL AS BEGIN INSERT INTO #Actual VALUES (''[Inner Test.Schema 1].[Temp Proc.edure 1] called'', @I); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX), I INT); + + EXEC tSQLt.SpyProcedure @ProcedureName = '[Inner Test.Schema 1].[Temp Proc.edure 1]', @CommandToExecute='EXEC @SpyProcedureOriginalObjectName 42;'; + + EXEC('[Inner Test.Schema 1].[Temp Proc.edure 1]'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'[Inner Test.Schema 1].[Temp Proc.edure 1] called',42); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test @SpyProcedureOriginalObjectName contains original proc name even if it has single quotes, dots, or spaces] +AS +BEGIN + EXEC('CREATE SCHEMA [I''nn''er Test.Schema 1];'); + EXEC('CREATE PROCEDURE [I''nn''er Test.Schema 1].[T''emp Proc.edure 1] @I INT = NULL + AS + BEGIN + INSERT INTO #Actual VALUES (''[I''''nn''''er Test.Schema 1].[T''''emp Proc.edure 1] called'', @I); + END;' + ); + + CREATE TABLE #Actual (Msg NVARCHAR(MAX), I INT); + + EXEC tSQLt.SpyProcedure @ProcedureName = '[I''nn''er Test.Schema 1].[T''emp Proc.edure 1]', @CommandToExecute='EXEC @SpyProcedureOriginalObjectName 42;'; + + + EXEC('[I''nn''er Test.Schema 1].[T''emp Proc.edure 1]'); + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES('[I''nn''er Test.Schema 1].[T''emp Proc.edure 1] called',42); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROC SpyProcedureTests.[test calls the original procedure even if @CommandToExecute contains inline comment] +AS +BEGIN + EXEC('CREATE PROCEDURE SpyProcedureTests.TempProcedure1 AS BEGIN INSERT INTO #Actual VALUES (''SpyProcedureTests.TempProcedure1 called''); END;'); + + CREATE TABLE #Actual (Id INT IDENTITY (1,1), Msg NVARCHAR(MAX)); + + EXEC tSQLt.SpyProcedure @ProcedureName = 'SpyProcedureTests.TempProcedure1', @CommandToExecute='DECLARE @II INT = 1;--PRINT @II;', @CallOriginal = 1; + + EXEC('SpyProcedureTests.TempProcedure1'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected VALUES(1,'SpyProcedureTests.TempProcedure1 called'); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO diff --git a/Tests/Tests.ssmssqlproj b/Tests/Tests.ssmssqlproj index c3a19eb54..66968e619 100644 --- a/Tests/Tests.ssmssqlproj +++ b/Tests/Tests.ssmssqlproj @@ -2,16 +2,26 @@ - + + + + + + + _ExploratoryTests.class.sql + AnnotationHostPlatformTests.class.sql + + AnnotationNoTransactionTests.class.sql + @@ -132,12 +142,6 @@ ExpectNoExceptionTests.class.sql - - - - - ExploratoryTests.class.sql - @@ -180,6 +184,21 @@ NewTestClassTests.class.sql + + Private_AssertNoSideEffectsTests.class.sql + + + + + + Private_CleanUpCmdHandlerTests.class.sql + + + + + + Private_CleanUpTests.class.sql + @@ -192,6 +211,12 @@ Private_GetAnnotationListTests.class.sql + + + + + Private_GetFormattedErrorInfoTests.class.sql + @@ -204,6 +229,12 @@ Private_GetSQLProductMajorVersionTests.class.sql + + + + + Private_HandleMessageAndResultTests.class.sql + @@ -222,11 +253,23 @@ Private_MarktSQLtTempObjectTests.class.sql - + + + + + Private_NoTransactionHandleTableTests.class.sql + + + + + + Private_NoTransactionTableActionTests.class.sql + + - Private_NullCellTableTests.class.sql + Private_NoTransactionHandleTablesTests.class.sql @@ -258,12 +301,24 @@ Private_ResetNewTestClassListTests.class.sql + + + + + Private_ResultsTests.class.sql + Private_ScriptIndexTests.class.sql + + + + + Private_SeizeTests.class.sql + @@ -307,9 +362,6 @@ ResultSetFilterTests.class.sql - - - Run_Methods_Tests.class.sql diff --git a/Tests/UndoTestDoublesTests.class.sql b/Tests/UndoTestDoublesTests.class.sql index 81cdac500..8040ac3d3 100644 --- a/Tests/UndoTestDoublesTests.class.sql +++ b/Tests/UndoTestDoublesTests.class.sql @@ -395,7 +395,7 @@ BEGIN EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); - EXEC tSQLt.ExpectException @ExpectedMessage = 'Cannot drop these objects as they are not marked as temporary. Use @Force = 1 to override. ([UndoTestDoublesTests].[SimpleTable1])', @ExpectedSeverity = 16, @ExpectedState = 10; + EXEC tSQLt.ExpectException @ExpectedMessage = 'Attempting to remove object(s) that is/are not marked as temporary. Use @Force = 1 to override. ([UndoTestDoublesTests].[SimpleTable1])', @ExpectedSeverity = 16, @ExpectedState = 10; EXEC tSQLt.UndoTestDoubles; END; @@ -415,7 +415,7 @@ BEGIN CREATE TABLE UndoTestDoublesTests.SimpleTable2 (i INT); CREATE TABLE UndoTestDoublesTests.SimpleTable3 (i INT); - EXEC tSQLt.ExpectException @ExpectedMessage = 'Cannot drop these objects as they are not marked as temporary. Use @Force = 1 to override. ([UndoTestDoublesTests].[SimpleTable1], [UndoTestDoublesTests].[SimpleTable2], [UndoTestDoublesTests].[SimpleTable3])', @ExpectedSeverity = 16, @ExpectedState = 10; + EXEC tSQLt.ExpectException @ExpectedMessage = 'Attempting to remove object(s) that is/are not marked as temporary. Use @Force = 1 to override. ([UndoTestDoublesTests].[SimpleTable1], [UndoTestDoublesTests].[SimpleTable2], [UndoTestDoublesTests].[SimpleTable3])', @ExpectedSeverity = 16, @ExpectedState = 10; EXEC tSQLt.UndoTestDoubles; END; @@ -433,7 +433,7 @@ BEGIN @level0type = N'SCHEMA', @level0name = 'UndoTestDoublesTests', @level1type = N'TABLE', @level1name = 'SimpleTable1'; - EXEC tSQLt.ExpectException @ExpectedMessage = 'Cannot drop these objects as they are not marked as temporary. Use @Force = 1 to override. ([UndoTestDoublesTests].[SimpleTable1])', @ExpectedSeverity = 16, @ExpectedState = 10; + EXEC tSQLt.ExpectException @ExpectedMessage = 'Attempting to remove object(s) that is/are not marked as temporary. Use @Force = 1 to override. ([UndoTestDoublesTests].[SimpleTable1])', @ExpectedSeverity = 16, @ExpectedState = 10; EXEC tSQLt.UndoTestDoubles; END; @@ -474,6 +474,10 @@ BEGIN EXEC tSQLt.RemoveObject @ObjectName='UndoTestDoublesTests.aSimpleTable'; EXEC ('CREATE PROCEDURE UndoTestDoublesTests.aSimpleTable AS PRINT ''Who came up with that name?'';'); EXEC tSQLt.SpyProcedure @ProcedureName = 'tSQLt.Private_Print'; + EXEC sys.sp_dropextendedproperty + @name = N'tSQLt.IsTempObject', + @level0type = N'SCHEMA', @level0name = 'tSQLt', + @level1type = N'TABLE', @level1name = 'Private_Print_SpyProcedureLog'; EXEC tSQLt.UndoTestDoubles @Force=1; @@ -481,12 +485,14 @@ BEGIN SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; INSERT INTO #Expected - VALUES('WARNING: @Force has been set to 1. Dropping the following objects that are not marked as temporary. ([UndoTestDoublesTests].[aSimpleTable])'); + VALUES('WARNING: @Force has been set to 1. Overriding the following error(s):Attempting to remove object(s) that is/are not marked as temporary. Use @Force = 1 to override. ([UndoTestDoublesTests].[aSimpleTable])'); EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; END; GO +/* --------------------------------------------------------------------------------------------- */ +GO CREATE PROCEDURE UndoTestDoublesTests.[test objects that are replaced multiple times by objects not marked as IsTempObject are restored if @Force=1] AS BEGIN @@ -519,7 +525,37 @@ BEGIN EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; END; GO -CREATE PROCEDURE UndoTestDoublesTests.[test drops a marked object even if it is not conflicting with an original object] +/* --------------------------------------------------------------------------------------------- */ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test non-testdouble object with @IsTempObjects=1 is also dropped] +AS +BEGIN + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1', @ObjectType=N'TABLE', @NewNameOfOriginalObject=NULL; + + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +/* --------------------------------------------------------------------------------------------- */ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test multiple non-testdouble objects with @IsTempObjects=1 are all dropped] AS BEGIN SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc @@ -527,8 +563,12 @@ BEGIN FROM sys.objects O; CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + CREATE TABLE UndoTestDoublesTests.SimpleTable2 (i INT); + CREATE TABLE UndoTestDoublesTests.SimpleTable3 (i INT); EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1', @ObjectType = 'TABLE', @NewNameOfOriginalObject = NULL; + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.SimpleTable2', @ObjectType = 'TABLE', @NewNameOfOriginalObject = NULL; + EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.SimpleTable3', @ObjectType = 'TABLE', @NewNameOfOriginalObject = NULL; EXEC tSQLt.UndoTestDoubles; @@ -546,21 +586,222 @@ BEGIN EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; END; GO -CREATE PROCEDURE UndoTestDoublesTests.[test drops multiple marked objects even if they do not conflict with an original object] +/** ------------------------------------------------------------------------------------------- **/ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test recovers table that was first faked and then removed] AS BEGIN + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc INTO #OriginalObjectIds FROM sys.objects O; + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.SimpleTable1'; + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +/** ------------------------------------------------------------------------------------------- **/ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test throws useful error if two objects with @IsTempObject<>1 need to be renamed to the same name] +AS +BEGIN + + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + + EXEC tSQLt.ExpectException @ExpectedMessagePattern = 'Attempting to rename two or more objects to the same name. Use @Force = 1 to override, only first object of each rename survives. ({[[]tSQLt_tempobject_%], [[]tSQLt_tempobject_%]}-->[[]UndoTestDoublesTests].[[]SimpleTable1])', @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.UndoTestDoubles; +END; +GO +/** ------------------------------------------------------------------------------------------- **/ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test throws useful error if there are multiple object tuples with @IsTempObject<>1 needing to be renamed to the same name] +AS +BEGIN + + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + CREATE TABLE UndoTestDoublesTests.SimpleTable2 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable2'; + CREATE TABLE UndoTestDoublesTests.SimpleTable2 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable2'; CREATE TABLE UndoTestDoublesTests.SimpleTable2 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable2'; CREATE TABLE UndoTestDoublesTests.SimpleTable3 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable3'; + CREATE TABLE UndoTestDoublesTests.SimpleTable3 (i INT); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable3'; + EXEC tSQLt.ExpectException @ExpectedMessagePattern = 'Attempting to rename two or more objects to the same name. Use @Force = 1 to override, only first object of each rename survives. ({[[]tSQLt_tempobject_%], [[]tSQLt_tempobject_%]}-->[[]UndoTestDoublesTests].[[]SimpleTable1]; {[[]tSQLt_tempobject_%], [[]tSQLt_tempobject_%], [[]tSQLt_tempobject_%]}-->[[]UndoTestDoublesTests].[[]SimpleTable2]; {[[]tSQLt_tempobject_%], [[]tSQLt_tempobject_%]}-->[[]UndoTestDoublesTests].[[]SimpleTable3])', @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.UndoTestDoubles; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test if two objects with @IsTempObject<>1 need to be renamed to the same name and @Force=1 the oldest survives] +AS +BEGIN + + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + INSERT INTO UndoTestDoublesTests.SimpleTable1 VALUES (6); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); + INSERT INTO UndoTestDoublesTests.SimpleTable1 VALUES (4); + EXEC tSQLt.RemoveObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1'; + + EXEC tSQLt.UndoTestDoubles @Force=1; + + SELECT i INTO #Actual FROM UndoTestDoublesTests.SimpleTable1; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES(6); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/** ------------------------------------------------------------------------------------------- **/ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test two objects with @IsTempObject<>1 and the same name but in different schemas are restored] +AS +BEGIN + EXEC('CREATE SCHEMA RandomSchema1;'); + CREATE TABLE RandomSchema1.SimpleTable1 (i INT); + INSERT INTO RandomSchema1.SimpleTable1 VALUES (4); + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema1.SimpleTable1'; + + EXEC('CREATE SCHEMA RandomSchema2;'); + CREATE TABLE RandomSchema2.SimpleTable1 (i INT); + INSERT INTO RandomSchema2.SimpleTable1 VALUES (6); + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema2.SimpleTable1'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT * INTO #Actual + FROM( + SELECT 'RandomSchema1' [schema_name], i FROM RandomSchema1.SimpleTable1 + UNION + SELECT 'RandomSchema2' [schema_name], i FROM RandomSchema2.SimpleTable1 + ) A + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected VALUES('RandomSchema1', 4),('RandomSchema2',6); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/** ------------------------------------------------------------------------------------------- **/ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test throws useful error if there are multiple object tuples in separate schemata but all with the same name] +AS +BEGIN + + EXEC('CREATE SCHEMA RandomSchema1;'); + EXEC('CREATE SCHEMA RandomSchema2;'); + + DECLARE @CurrentTableObjectId INT + CREATE TABLE RandomSchema1.SimpleTable1 (i INT); + SET @CurrentTableObjectId = OBJECT_ID('RandomSchema1.SimpleTable1'); + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema1.SimpleTable1'; + DECLARE @Name1A NVARCHAR(MAX) = OBJECT_NAME(@CurrentTableObjectId); + + CREATE TABLE RandomSchema1.SimpleTable1 (i INT); + SET @CurrentTableObjectId = OBJECT_ID('RandomSchema1.SimpleTable1'); + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema1.SimpleTable1'; + DECLARE @Name1B NVARCHAR(MAX) = OBJECT_NAME(@CurrentTableObjectId); + + CREATE TABLE RandomSchema2.SimpleTable1 (i INT); + SET @CurrentTableObjectId = OBJECT_ID('RandomSchema2.SimpleTable1'); + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema2.SimpleTable1'; + DECLARE @Name2A NVARCHAR(MAX) = OBJECT_NAME(@CurrentTableObjectId); + + CREATE TABLE RandomSchema2.SimpleTable1 (i INT); + SET @CurrentTableObjectId = OBJECT_ID('RandomSchema2.SimpleTable1'); + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema2.SimpleTable1'; + DECLARE @Name2B NVARCHAR(MAX) = OBJECT_NAME(@CurrentTableObjectId); + + DECLARE @ExpectedMessage NVARCHAR(MAX) = 'Attempting to rename two or more objects to the same name. Use @Force = 1 to override, only first object of each rename survives. ({['+@Name1A+'], ['+@Name1B+']}-->[RandomSchema1].[SimpleTable1]; {['+@Name2A+'], ['+@Name2B+']}-->[RandomSchema2].[SimpleTable1])' + EXEC tSQLt.ExpectException @ExpectedMessage = @ExpectedMessage, @ExpectedSeverity = 16, @ExpectedState = 10; + + EXEC tSQLt.UndoTestDoubles; +END; +GO +/** ------------------------------------------------------------------------------------------- **/ +GO +CREATE PROCEDURE UndoTestDoublesTests.[test can handle the kitchen sink] +AS +BEGIN + EXEC('CREATE SCHEMA RandomSchema1;'); + EXEC('CREATE SCHEMA RandomSchema2;'); + CREATE TABLE RandomSchema1.SimpleTable1 (i INT); + CREATE TABLE RandomSchema1.SimpleTable2 (i INT CONSTRAINT [s1t2pk] PRIMARY KEY); + CREATE TABLE RandomSchema1.SimpleTable3 (i INT); + + CREATE TABLE RandomSchema2.SimpleTable1 (i INT); + CREATE TABLE RandomSchema2.SimpleTable2 (i INT); + CREATE TABLE RandomSchema2.SimpleTable3 (i INT); + + EXEC('CREATE PROCEDURE RandomSchema1.Proc1 AS RETURN;') + EXEC('CREATE PROCEDURE RandomSchema1.Proc2 AS RETURN;') + EXEC('CREATE PROCEDURE RandomSchema1.Proc3 AS RETURN;') + + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.FakeTable @TableName = 'RandomSchema1.SimpleTable1'; + CREATE TABLE UndoTestDoublesTests.SimpleTable1 (i INT); EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.SimpleTable1', @ObjectType = 'TABLE', @NewNameOfOriginalObject = NULL; + EXEC tSQLt.SpyProcedure @ProcedureName = 'RandomSchema1.Proc1'; + EXEC tSQLt.FakeTable @TableName = 'RandomSchema1.SimpleTable1'; + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema1.SimpleTable3'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'RandomSchema1.Proc1'; + EXEC tSQLt.FakeTable @TableName = 'RandomSchema1.SimpleTable2'; + CREATE TABLE UndoTestDoublesTests.SimpleTable2 (i INT); EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.SimpleTable2', @ObjectType = 'TABLE', @NewNameOfOriginalObject = NULL; + + EXEC tSQLt.FakeTable @TableName = 'RandomSchema1.SimpleTable1'; + EXEC tSQLt.ApplyConstraint @TableName = 'RandomSchema1.SimpleTable2', @ConstraintName = 's1t2pk'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'RandomSchema1.Proc1'; + EXEC tSQLt.FakeTable @TableName = 'RandomSchema2.SimpleTable3'; + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema1.Proc3'; + EXEC tSQLt.FakeTable @TableName = 'RandomSchema1.SimpleTable2'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'RandomSchema1.Proc1'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'RandomSchema1.Proc2'; + EXEC tSQLt.SpyProcedure @ProcedureName = 'RandomSchema1.Proc1'; + EXEC tSQLt.FakeTable @TableName = 'RandomSchema2.SimpleTable1'; + EXEC tSQLt.FakeTable @TableName = 'RandomSchema2.SimpleTable3'; + CREATE TABLE UndoTestDoublesTests.SimpleTable3 (i INT); EXEC tSQLt.Private_MarktSQLtTempObject @ObjectName = 'UndoTestDoublesTests.SimpleTable3', @ObjectType = 'TABLE', @NewNameOfOriginalObject = NULL; + EXEC tSQLt.ApplyConstraint @TableName = 'RandomSchema1.SimpleTable2', @ConstraintName = 's1t2pk'; + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema1.SimpleTable2'; + EXEC tSQLt.RemoveObject @ObjectName = 'RandomSchema1.SimpleTable1'; + EXEC tSQLt.UndoTestDoubles; SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc @@ -575,5 +816,9 @@ BEGIN SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A ) T; EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; + END; GO +/*-----------------------------------------------------------------------------------------------*/ +GO + diff --git a/Tests/UninstallTests.class.sql b/Tests/UninstallTests.class.sql index c37adc9ba..7af78b590 100644 --- a/Tests/UninstallTests.class.sql +++ b/Tests/UninstallTests.class.sql @@ -141,7 +141,7 @@ BEGIN @Message = @FailureMessage; END; GO -CREATE PROCEDURE UninstallTests.[-test Uninstall does not fail if the tSQLtCLR assembly is missing] +CREATE PROCEDURE UninstallTests.[test Uninstall does not fail if the tSQLtCLR assembly is missing] AS BEGIN DECLARE @id INT; @@ -150,6 +150,14 @@ BEGIN DECLARE @TranName CHAR(32); EXEC tSQLt.GetNewTranName @TranName OUT; SAVE TRAN @TranName; + DROP PROCEDURE tSQLt.ResultSetFilter; + DROP PROCEDURE tSQLt.AssertResultSetsHaveSameMetaData; + DROP PROCEDURE tSQLt.NewConnection; + DROP PROCEDURE tSQLt.CaptureOutput; + DROP PROCEDURE tSQLt.SuppressOutput; + DROP FUNCTION tSQLt.Private_GetAnnotationList; + DROP TYPE tSQLt.[Private]; + DROP ASSEMBLY tSQLtCLR; BEGIN TRY diff --git a/Tests/_ExploratoryTests.class.sql b/Tests/_ExploratoryTests.class.sql new file mode 100644 index 000000000..633043507 --- /dev/null +++ b/Tests/_ExploratoryTests.class.sql @@ -0,0 +1,366 @@ +EXEC tSQLt.NewTestClass '_ExploratoryTests'; +GO +CREATE PROCEDURE [_ExploratoryTests].[test NULL can be CAST into any datatype] +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX) = + ( + SELECT ',CAST(NULL AS '+QUOTENAME(SCHEMA_NAME(schema_id))+'.'+QUOTENAME(name)+')['+CAST(user_type_id AS NVARCHAR(MAX))+']' + FROM sys.types + WHERE is_user_defined = 0 + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)'); + SET @cmd = STUFF(@cmd,1,1,''); + SET @cmd = 'SELECT TOP(0) '+@cmd+' INTO [_ExploratoryTests].DataTypeTestTable;' + EXEC(@cmd); + SELECT * + INTO #Actual + FROM sys.columns + WHERE object_id = OBJECT_ID('[_ExploratoryTests].DataTypeTestTable') + AND name <> user_type_id + EXEC tSQLt.AssertEmptyTable @TableName = '#Actual'; +END; +GO + +CREATE PROCEDURE [_ExploratoryTests].[test MSSQL preserves COLLATION when using SELECT INTO] +AS +BEGIN + SELECT + 'Hello World!' COLLATE SQL_Polish_CP1250_CI_AS c1, + 'Hello World!' COLLATE SQL_Latin1_General_CP437_BIN c2, + 'Hello World!' COLLATE Albanian_BIN2 c3 + INTO [_ExploratoryTests].Table1 + + SELECT * INTO [_ExploratoryTests].Table2 FROM [_ExploratoryTests].Table1 + + EXEC tSQLt.AssertEqualsTableSchema @Expected = '[_ExploratoryTests].Table1', @Actual = '[_ExploratoryTests].Table2'; +END; +GO +CREATE PROCEDURE [_ExploratoryTests].[test MSSQL creates INTO table before processing] +AS +BEGIN + SELECT 1 X INTO #Test; + DECLARE @NotExpected INT = OBJECT_ID('tempdb..#Test'); + DECLARE @Actual INT; + + EXEC sys.sp_executesql N' + SELECT * INTO #Test FROM tempdb.sys.objects C WHERE C.object_id = OBJECT_ID(''tempdb..#Test''); + EXEC sys.sp_executesql N''SELECT @Actual = object_id FROM #Test;'',N''@Actual INT OUTPUT'',@Actual OUT;', + N'@Actual INT OUTPUT', + @Actual OUT; + + EXEC tSQLt.AssertNotEquals @Expected = @NotExpected, @Actual = @Actual; +END; +GO +CREATE PROCEDURE [_ExploratoryTests].[test MSSQL creates INTO table after compiling] +AS +BEGIN + SELECT 1 X INTO #Test; + DECLARE @NotExpected INT = OBJECT_ID('tempdb..#Test'); + DECLARE @Actual INT; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'Invalid column name ''object_id''.'; + EXEC sys.sp_executesql N' + SELECT * INTO #Test FROM tempdb.sys.objects C WHERE C.object_id = OBJECT_ID(''tempdb..#Test''); + SELECT @Actual = object_id FROM #Test;', + N'@Actual INT OUTPUT', + @Actual OUT; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test FOR XML returns NULL for empty result set] +AS +BEGIN + SELECT ((SELECT 1 WHERE 1 = 0 FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)')) [FOR XML FROM EMPTY] INTO #Actual; + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES(NULL); + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test CURSOR_STATUS indicates whether a cursor variable is set (and other things)] +AS +BEGIN + CREATE TABLE #Actual (Situation NVARCHAR(MAX), [Cursor Status] INT, [Fetch Status] INT); + EXEC('INSERT INTO #Actual SELECT ''Variable not defined'', CURSOR_STATUS(''variable'',''@ACursor''),NULL;'); + DECLARE @ACursor CURSOR; + INSERT INTO #Actual SELECT 'Variable defined (DECLARE)', CURSOR_STATUS('variable','@ACursor'), NULL; + SET @ACursor = CURSOR FOR SELECT 1; + INSERT INTO #Actual SELECT 'Variable allocated (SET)', CURSOR_STATUS('variable','@ACursor'), NULL; + OPEN @ACursor; + INSERT INTO #Actual SELECT 'Cursor opened', CURSOR_STATUS('variable','@ACursor'), NULL; + DECLARE @IgnoreThis INT; + FETCH NEXT FROM @ACursor INTO @IgnoreThis; + INSERT INTO #Actual SELECT 'Cursor after fetch', CURSOR_STATUS('variable','@ACursor'), @@FETCH_STATUS; + FETCH NEXT FROM @ACursor INTO @IgnoreThis; + INSERT INTO #Actual SELECT 'Cursor after final fetch', CURSOR_STATUS('variable','@ACursor'), @@FETCH_STATUS; + CLOSE @ACursor; + INSERT INTO #Actual SELECT 'Cursor closed', CURSOR_STATUS('variable','@ACursor'), NULL; + DEALLOCATE @ACursor; + INSERT INTO #Actual SELECT 'Cursor deallocated', CURSOR_STATUS('variable','@ACursor'), NULL; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES + ('Variable not defined',-3,NULL), + ('Variable defined (DECLARE)',-2,NULL), + ('Variable allocated (SET)',-1,NULL), + ('Cursor opened',1,NULL), + ('Cursor after fetch',1,0), + ('Cursor after final fetch',1,-1), + ('Cursor closed',-1,NULL), + ('Cursor deallocated',-2,NULL); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test CURSOR will not be passed out through OUTPUT @parameter if it has not been opened (CURSOR_STATUS = -1)] +AS +BEGIN + DECLARE @CursorProc NVARCHAR(MAX) = '[_ExploratoryTests].CursorProc'; + EXEC(' + CREATE PROCEDURE '+@CursorProc+' + @CursorParameter CURSOR VARYING OUTPUT + AS + BEGIN + SET @CursorParameter = CURSOR FOR SELECT 42; + END; + '); + + DECLARE @CursorVariable CURSOR; + + EXEC @CursorProc @CursorParameter = @CursorVariable OUTPUT; + + CREATE TABLE #Actual ([Cursor Status] INT); + INSERT INTO #Actual SELECT CURSOR_STATUS('variable','@CursorVariable'); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES(-2); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test CURSOR can be passed out through OUTPUT @parameter if it has been opened (CURSOR_STATUS = 1)] +AS +BEGIN + DECLARE @CursorProc NVARCHAR(MAX) = '[_ExploratoryTests].CursorProc'; + EXEC(' + CREATE PROCEDURE '+@CursorProc+' + @CursorParameter CURSOR VARYING OUTPUT + AS + BEGIN + SET @CursorParameter = CURSOR FOR SELECT 42; + OPEN @CursorParameter; + END; + '); + + DECLARE @IntValue INT = NULL; + DECLARE @CursorVariable CURSOR; + + EXEC @CursorProc @CursorParameter = @CursorVariable OUTPUT; + + FETCH NEXT FROM @CursorVariable INTO @IntValue; + CLOSE @CursorVariable; + DEALLOCATE @CursorVariable; + + EXEC tSQLt.AssertEquals @Expected = 42, @Actual = @IntValue; +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test CURSOR cannot be passed to a proc with OUTPUT specified in the call if it has been allocated (SET, CURSOR_STATUS = -1)] +AS +BEGIN + DECLARE @CursorProc NVARCHAR(MAX) = '[_ExploratoryTests].CursorProc'; + EXEC(' + CREATE PROCEDURE '+@CursorProc+' + @CursorParameter CURSOR VARYING OUTPUT + AS + BEGIN + RETURN; + END; + '); + + DECLARE @IntValue INT = NULL; + DECLARE @CursorVariable CURSOR; + SET @CursorVariable = CURSOR FOR SELECT 13; + + EXEC tSQLt.ExpectException @ExpectedMessage = 'The variable ''@CursorVariable'' cannot be used as a parameter because a CURSOR OUTPUT parameter must not have a cursor allocated to it before execution of the procedure.'; + EXEC @CursorProc @CursorParameter = @CursorVariable OUTPUT; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test CURSOR can be passed to a proc without OUTPUT specified in the call even if it has been allocated (SET, CURSOR_STATUS = -1)] +AS +BEGIN + DECLARE @CursorProc NVARCHAR(MAX) = '[_ExploratoryTests].CursorProc'; + EXEC(' + CREATE PROCEDURE '+@CursorProc+' + @CursorParameter CURSOR VARYING OUTPUT + AS + BEGIN + RETURN; + END; + '); + + DECLARE @IntValue INT = NULL; + DECLARE @CursorVariable CURSOR; + SET @CursorVariable = CURSOR FOR SELECT 13; + + EXEC tSQLt.ExpectNoException; + EXEC @CursorProc @CursorParameter = @CursorVariable; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test CURSOR gets deallocated when its variable goes out of scope?] +AS +BEGIN + SELECT IDENTITY(INT,1,1) AS ID, name, is_open, fetch_status + INTO #Actual FROM (SELECT NULL)ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = '@ACursor'; + + EXEC(' + DECLARE @ACursor CURSOR; + SET @ACursor = CURSOR FOR SELECT 42; + INSERT INTO #Actual SELECT name, is_open, fetch_status + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''@ACursor''; + OPEN @ACursor; + INSERT INTO #Actual SELECT name, is_open, fetch_status + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''@ACursor''; + DECLARE @I INT; FETCH NEXT FROM @ACursor INTO @I; + INSERT INTO #Actual SELECT name, is_open, fetch_status + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''@ACursor''; + '); + + INSERT INTO #Actual SELECT name, is_open, fetch_status + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = '@ACursor'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES + (1,NULL,NULL,NULL), + (2,'@ACursor','false', -9), + (3,'@ACursor','true', -9), + (4,'@ACursor','true', 0), + (5,NULL,NULL,NULL); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test LOCAL CURSOR (not a variable) gets deallocated when it goes out of scope?] +AS +BEGIN + SELECT IDENTITY(INT,1,1) AS ID, name, is_open, fetch_status, CURSOR_STATUS('local','ACursor') AS [cursor status] + INTO #Actual FROM (SELECT NULL)ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = 'ACursor'; + + EXEC(' + DECLARE ACursor CURSOR LOCAL FOR SELECT 42; + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS(''local'',''ACursor'') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''ACursor''; + OPEN ACursor; + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS(''local'',''ACursor'') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''ACursor''; + DECLARE @I INT; FETCH NEXT FROM ACursor INTO @I; + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS(''local'',''ACursor'') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''ACursor''; + '); + + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS('local','ACursor') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = 'ACursor'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES + (1,NULL,NULL,NULL, -3), + (2,'ACursor','false', -9, -1), + (3,'ACursor','true', -9, 1), + (4,'ACursor','true', 0, 1), + (5,NULL,NULL,NULL, -3); + + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test GLOBAL CURSOR (not a variable) gets deallocated when it goes out of scope?] +AS +BEGIN + SELECT IDENTITY(INT,1,1) AS ID, name, is_open, fetch_status, CURSOR_STATUS('global','ACursor') AS [cursor status] + INTO #Actual FROM (SELECT NULL)ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = 'ACursor'; + + EXEC(' + DECLARE ACursor CURSOR /*GLOBAL*/ FOR SELECT 42; + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS(''global'',''ACursor'') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''ACursor''; + OPEN ACursor; + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS(''global'',''ACursor'') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''ACursor''; + DECLARE @I INT; FETCH NEXT FROM ACursor INTO @I; + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS(''global'',''ACursor'') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = ''ACursor''; + '); + + INSERT INTO #Actual SELECT name, is_open, fetch_status, CURSOR_STATUS('global','ACursor') + FROM (SELECT NULL )ID(ID) LEFT JOIN sys.dm_exec_cursors(@@SPID) ON name = 'ACursor'; + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES + (1,NULL,NULL,NULL, -3), + (2,'ACursor','false', -9, -1), + (3,'ACursor','true', -9, 1), + (4,'ACursor','true', 0, 1), + (5,'ACursor','true', 0, 1); + BEGIN TRY CLOSE ACursor; END TRY BEGIN CATCH END CATCH; + BEGIN TRY DEALLOCATE ACursor; END TRY BEGIN CATCH END CATCH; + EXEC tSQLt.AssertEqualsTable '#Expected','#Actual'; + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +CREATE PROCEDURE [_ExploratoryTests].[test sp_addextendedproperty can handle odd values] +AS +BEGIN + CREATE TABLE [_ExploratoryTests].ATable(I INT); + + EXEC sys.sp_addextendedproperty + @name = N'ATestProperty', + @value = 'a string.with''special chars', + @level0type = N'SCHEMA', @level0name = '_ExploratoryTests', + @level1type = 'TABLE', @level1name = 'ATable'; + + SELECT * FROM sys.extended_properties AS EP WHERE EP.major_id = OBJECT_ID('[_ExploratoryTests].ATable'); + +END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO +--CREATE PROCEDURE [_ExploratoryTests].[test TBD] +--AS +--BEGIN +-- EXEC tSQLt.Fail 'TemplateTest'; +--END; +GO +/*-----------------------------------------------------------------------------------------------*/ +GO + +--EXEC tSQLt.Run [_ExploratoryTests] \ No newline at end of file diff --git a/Tests/tSQLt_test.class.sql b/Tests/tSQLt_test.class.sql index 31fa05afd..5107cdb49 100644 --- a/Tests/tSQLt_test.class.sql +++ b/Tests/tSQLt_test.class.sql @@ -95,30 +95,60 @@ GO CREATE PROC tSQLt_test.test_Run_handles_uncommitable_transaction AS +BEGIN + DECLARE @TranName sysname; + + SELECT TOP(1) @TranName = TranName FROM tSQLt.TestResult WHERE Class = 'tSQLt_test' AND TestCase = 'test_Run_handles_uncommitable_transaction' ORDER BY Id DESC; + EXEC ('CREATE PROC tSQLt_test.testUncommitable00A1030051764AE7A946E827159E7063 AS BEGIN CREATE TABLE t1 (i int); CREATE TABLE t1 (i int); END;'); + BEGIN TRY + EXEC tSQLt.Run 'tSQLt_test.testUncommitable00A1030051764AE7A946E827159E7063'; + END TRY + BEGIN CATCH + --Left intentionally empty + END CATCH; + + SELECT Class, TestCase, Result, Msg + INTO #Actual + FROM tSQLt.TestResult + WHERE TestCase = 'testUncommitable00A1030051764AE7A946E827159E7063'; + + SELECT TOP(0) A.Class,A.TestCase,A.Result,A.Msg AS [%Msg] INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + + INSERT INTO #Expected + SELECT 'tSQLt_test' Class, 'testUncommitable00A1030051764AE7A946E827159E7063' TestCase, 'Error' Result, '%There is already an object named ''t1'' in the database.%The current transaction cannot be committed and cannot be rolled back to a savepoint.%' [%Msg]; + + SELECT * INTO #Compare + FROM( + SELECT '>' _R_,* FROM #Actual AS A WHERE NOT EXISTS(SELECT 1 FROM #Expected E WHERE A.Class = E.Class AND A.TestCase = E.TestCase AND A.Result = E.Result AND A.Msg LIKE E.[%Msg]) + UNION ALL + SELECT '<' _R_,* FROM #Expected AS E WHERE NOT EXISTS(SELECT 1 FROM #Actual A WHERE A.Class = E.Class AND A.TestCase = E.TestCase AND A.Result = E.Result AND A.Msg LIKE E.[%Msg]) + )X; + IF(@@ROWCOUNT>0) + BEGIN + INSERT INTO #Compare + SELECT '=' _R_,* FROM (SELECT * FROM #Actual INTERSECT SELECT * FROM #Expected)X; + END; + EXEC tSQLt.AssertEmptyTable '#Compare'; + + DELETE FROM tSQLt.TestResult + WHERE TestCase = 'testUncommitable00A1030051764AE7A946E827159E7063'; + BEGIN TRAN; + SAVE TRAN @TranName; +END; +GO + + +CREATE PROC tSQLt_test.test_Run_ROLLsBACK_uncommitable_transaction +AS BEGIN DECLARE @TranName sysname; - DECLARE @ProductMajorVersion INT; - EXEC @ProductMajorVersion = tSQLt.Private_GetSQLProductMajorVersion; + SELECT TOP(1) @TranName = TranName FROM tSQLt.TestResult WHERE Class = 'tSQLt_test' AND TestCase = 'test_Run_ROLLsBACK_uncommitable_transaction' ORDER BY Id DESC; - SELECT TOP(1) @TranName = TranName FROM tSQLt.TestResult WHERE Class = 'tSQLt_test' AND TestCase = 'test_Run_handles_uncommitable_transaction' ORDER BY Id DESC; EXEC ('CREATE PROC tSQLt_test.testUncommitable00A1030051764AE7A946E827159E7063 AS BEGIN CREATE TABLE t1 (i int); CREATE TABLE t1 (i int); END;'); BEGIN TRY EXEC tSQLt.Run 'tSQLt_test.testUncommitable00A1030051764AE7A946E827159E7063'; END TRY BEGIN CATCH - --SELECT * FROM tSQLt.TestResult WHERE TestCase = 'testUncommitable00A1030051764AE7A946E827159E7063'; - IF NOT EXISTS(SELECT 1 - FROM tSQLt.TestResult - WHERE TestCase = 'testUncommitable00A1030051764AE7A946E827159E7063' - AND Result = 'Error' - AND Msg LIKE '%There is already an object named ''t1'' in the database.[[]%]{'+ - CASE WHEN @ProductMajorVersion >= 14 THEN 'tSQLt_test.' ELSE '' END+ - 'testUncommitable00A1030051764AE7A946E827159E7063,1}%' - AND Msg LIKE '%The current transaction cannot be committed and cannot be rolled back to a savepoint.%' - ) - BEGIN - EXEC tSQLt.Fail 'tSQLt.Run ''tSQLt_test.testUncommitable00A1030051764AE7A946E827159E7063'' did not error correctly'; - END; IF(@@TRANCOUNT > 0) BEGIN EXEC tSQLt.Fail 'tSQLt.Run ''tSQLt_test.testUncommitable00A1030051764AE7A946E827159E7063'' did not rollback the transactions'; @@ -1310,7 +1340,7 @@ BEGIN SELECT '{' + Msg + '}' AS BracedMsg INTO #actual - FROM tSQLt.TestMessage; + FROM #TestMessage; SELECT TOP(0) * INTO #expected @@ -1333,7 +1363,7 @@ BEGIN SELECT '{' + Msg + '}' AS BracedMsg INTO #actual - FROM tSQLt.TestMessage; + FROM #TestMessage; SELECT TOP(0) * INTO #expected diff --git a/Tests/tSQLtclr_test.class.sql b/Tests/tSQLtclr_test.class.sql index 37fb16300..5a3ce3b9e 100644 --- a/Tests/tSQLtclr_test.class.sql +++ b/Tests/tSQLtclr_test.class.sql @@ -184,4 +184,14 @@ BEGIN END; END; GO + +CREATE PROC tSQLtclr_test.[test name returned by tSQLt.Private::CreateUniqueObjectName() should not require quoting] +AS +BEGIN + DECLARE @NewName NVARCHAR(MAX) = tSQLt.Private::CreateUniqueObjectName(); + + EXEC tSQLt.ExpectNoException; + EXEC('CREATE TABLE tSQLtclr_test.'+@NewName+'(I INT);'); +END; +GO --ROLLBACK diff --git a/tSQLtCLR/tSQLtTestUtilCLR/ClrStoredProcedures.cs b/tSQLtCLR/tSQLtTestUtilCLR/ClrStoredProcedures.cs new file mode 100644 index 000000000..cf36112dc --- /dev/null +++ b/tSQLtCLR/tSQLtTestUtilCLR/ClrStoredProcedures.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace tSQLtTestUtilCLR +{ + public class ClrStoredProcedures + { + public static void AClrSsp() + { + return; + } + + } +} diff --git a/tSQLtCLR/tSQLtTestUtilCLR/tSQLtTestUtilCLR.csproj b/tSQLtCLR/tSQLtTestUtilCLR/tSQLtTestUtilCLR.csproj index 100270f54..977061726 100644 --- a/tSQLtCLR/tSQLtTestUtilCLR/tSQLtTestUtilCLR.csproj +++ b/tSQLtCLR/tSQLtTestUtilCLR/tSQLtTestUtilCLR.csproj @@ -55,6 +55,7 @@ +