Is there a way to test other assertions inside a test after calling tSQLt.ExpectException? - tsqlt

Using tSQLt 1.0.5873.27393, I'm trying to write a tSQLt test for a stored procedure that records errors trapped in a CATCH block to a log table before re-throwing the error to the calling session.
I can successfully test that the error message is re-thrown using tSQLt.ExpectException, but a side effect seems to be that it's not possible to test any other assertions after calling tSQLt.ExpectException - so I am unable to test whether the error has been written to the log table.
The simplest test that I can come up with which demonstrates the problem is:
CREATE PROC MyTests.[test ExpectException]
AS
BEGIN
EXEC tSQLt.ExpectException;
THROW 50001, 'Error message',1
EXEC tSQLt.AssertEquals 0,1, '0=1'
END
GO
which produces the following (unexpected) output when executed:
|No|Test Case Name |Dur(ms)|Result |
+--+--------------------------------+-------+-------+
|1 |[MyTests].[test ExpectException]| 3|Success|
Using a profiler trace, I can see that the AssertEquals assertion is never executed, because the error is trapped by a CATCH block inside tSQLt.Private_RunTest.
Because tSQLt uses CATCH to trap errors, I suspect that there's no solution to this problem without a major rewrite of tSQLt - but I ask here just in case others have found a way around the issue.

You could follow this approach:
In the test, wrap your call to the tested stored procedure in a try/catch block.
Before the try/catch block, set up an expected variable to 1 and actual to 0.
In the test catch block, check if the log table got populated and if yes then flip the actual variable to 1.
Write your assertion AFTER the catch block.
CREATE PROC MyTests.[test ExpectException]
AS
BEGIN
DECLARE #expected bit = 1;
DECLARE #actual bit = 0;
BEGIN TRY
-- call tested SP, make sure it fails
CALL SP..;
-- add safety net, if the SP call doesn't fail then fail the test
RAISERROR('Fail the test', 16, 1);
END TRY
BEGIN CATCH
-- pseudo code
IF EXISTS (SELECT 1 FROM log table)
-> flip #actual to 1
END CATCH
EXEC tSQLt.AssertEquals #expected, #actual
END
GO

#Eduard Uta got there before I did, but I'll post this anyway.
It dawned on me almost as soon as I'd posted this that the obvious solution is to use a custom TRY...CATCH block, rather than the built-in tSQLt objects:
CREATE PROC MyTests.[test custom try catch]
AS
BEGIN
DECLARE #ErrorRaised bit = 0
BEGIN TRY
THROW 50001, 'Error message',1
END TRY
BEGIN CATCH
SET #ErrorRaised = 1
END CATCH
EXEC tSQLt.AssertEquals 1,#ErrorRaised, 'Error raised'
EXEC tSQLt.AssertEquals 0,1, '0=1'
END
GO
Here's an example which tests for a specific error message:
CREATE PROC MyTests.[test custom try catch test message]
AS
BEGIN
DECLARE #ErrorRaised bit = 0
BEGIN TRY
THROW 50001, 'Error message',1
END TRY
BEGIN CATCH
IF ERROR_MESSAGE() = 'Error message'
SET #ErrorRaised = 1
ELSE
THROW
END CATCH
EXEC tSQLt.AssertEquals 1,#ErrorRaised, 'Error raised'
EXEC tSQLt.AssertEquals 0,1, '0=1'
END
GO

For me, calling both the procedure under test and the tSQLt ExpectException proc inside a TRY...CATCH block seems to have done the trick.
CREATE PROC MyTests.[test ExpectException]
AS
BEGIN
BEGIN TRY
-- Call the proc that raises the error
EXEC offending_stored_proc
-- Tell tSQLt to expect the exception
EXEC tSQLt.ExpectException #ExpectedMessage = 'Expected error message', #ExpectedSeverity = NULL, #ExpectedState = NULL;
END TRY
BEGIN CATCH
-- No need to do anything here
END CATCH
END;
As expected, if the procedure raises the error with that error message, the test passes. If the error is not raised, the test fails.

Related

how to avoid tSQLt to abort the called proc after raiserror?

In our stored procedures , we like to "return" a specific return value after raising and error using raiserror()
For that to work, we use low severity levels (eg 12), followed by a return value, such as:
create or alter proc dbo.usp_test
as
begin
print '**start of proc'
raiserror( 'error on purpose',12,1);
print '**after the error'
return -3
end
This work perfectly when running that procedure:
declare #RC int
exec #RC = dbo.usp_test
print concat('The return code is ', #RC)
/* output:
**start of proc
Msg 50000, Level 12, State 1, Procedure usp_test, Line 6 [Batch Start Line 12]
error on purpose
**after the error
The return code is -3
*/
However, when that proc is called from a unit test, the behaviour is different, suddenly the execution is stopped after the raiserror:
create schema T_usp_test authorization dbo;
GO
EXECUTE sp_addextendedproperty #name = 'tSQLt.TestClass', #value = 1, #level0type = 'SCHEMA', #level0name = 'T_usp_test'
GO
create or alter proc T_usp_test.[test mytest]
as
begin
exec tSQLt.ExpectException;
declare #RC int;
exec #RC = dbo.usp_test;
print concat('The return code is ', #RC)
end
GO
exec tSQLt.Run 'T_usp_test.[test mytest]'
/* output:
(1 row affected)
**start of proc
+----------------------+
|Test Execution Summary|
+----------------------+
|No|Test Case Name |Dur(ms)|Result |
+--+--------------------------+-------+-------+
|1 |[T_usp_test].[test mytest]| 7|Success|
-----------------------------------------------------------------------------
Test Case Summary: 1 test case(s) executed, 1 succeeded, 0 failed, 0 errored.
-----------------------------------------------------------------------------
*/
So the question:
1) why is the behaviour different and is the proc now suddenly stopping executing at raiserror() ?
2) how can I overcome this ?
The core proc that runs the test is tSQLt.Private_RunTest which runs the test in a TRY...CATCH block. From the docs at https://learn.microsoft.com/en-us/sql/t-sql/language-elements/try-catch-transact-sql?view=sql-server-2017 :
A TRY...CATCH construct catches all execution errors that have a severity higher than 10 that do not close the database connection.
It looks like you've set the severity to 12. Consider lowering the severity of your RAISERROR call and see if that changes the behavior. You could also try wrapping your RAISERROR call in its own TRY...CATCH block.

Ada suppress unreachable code or missing return

I have a tagged type that implements a number of functions. In one case I need one of these functions to instead enter an infinite loop. Unfortunately as far as I can tell there is no way for me to compile this such that it doesn't raise a warning. I wish to still use -gnatwe to ensure there are no warnings in my code, but how can I implement such a function.
Here is what the function looks like:
function Foo (This : Some_Type) return Some_Type'Class is
begin
loop
Do_Useful_Stuff_Indefinitely_With (This);
end loop;
-- return This; (if needed?)
end Foo;
I have tried pragma (No_Return) except that is only applicable for procedures (and the Foo function is used as a proper function elsewhere so must have the same signature).
I also tried pragma Suppress (All_Checks) but that still raised a warning for unreachable code or missing return statement error.
Is there any way whatsoever to have a once-off function that runs forever without raising a warning?
Taking the same example that Jean-François provided, you can avoid the warning by declaring and calling a "private" procedure (you don't have to declare it in spec) wrapping your loop as in the following :
package body foo is
procedure Infinite_Loop is
begin
loop
null;
end loop;
end Infinite_Loop;
function bar return integer is
begin
Infinite_Loop;
return 12;
end bar;
end foo;
pragma Suppress (All_Checks) acts on run-time checks. Won't help you there. Leave that alone unless you focus on performance, but then you have -p option to do it using command line switches
The pragma Suppress suppresses compiler-generated run-time checks. If a run-time check is disabled, an exception may be suppressed and undefined behavior could result. pragma Suppress is used at the programmer's risk.
You need the return statement, but you can wrap it around 2 pragma warnings statements (A case where have you tried turning it off and on again? works)
pragma warnings(off,"unreachable code");
return This;
pragma warnings(on,"unreachable code");
note that the text is optional but enables to filter other warnings that could occur (if needed). It's better since turning off all warnings is generally bad practice.
Note that you have to turn warnings on again after the statement.
Self-contained demo. foo.adb looks like:
package body foo is
function bar return integer is
begin
loop
null;
end loop;
pragma warnings(off,"unreachable code");
return 12;
pragma warnings(on,"unreachable code");
end bar;
end foo;
foo.ads looks like:
package foo is
function bar return integer;
end foo;
If I comment out the pragma lines:
$ gcc -c -gnatwe foo.adb
foo.adb:8:05: warning: unreachable code
uncommenting them removes the warning.

Starting tasks again

I recently started Ada programming and now I'm stuck.
I created a program with multiple tasks. The main-task is managing incoming communication and as a consequence starts working-tasks or transfers data to the working-tasks.
The working-tasks are all of the same kind but with different identifiers.
They do their work and should finish after that. For example:
task body Access_Protected is
begin
accept Start(foo: in Integer; foo2: out Integer)
do something
end Start;
while Go_loop loop
select
accept Quit do
Go_loop := false;
end Quit;
or
accept Insert(foo3: in Integer)
do something
if something = 0 then
Go_loop := false;
end if;
end Insert;
or delay 2.0;
end select;
end loop;
end Access_Protected;
I understand that the working-task should be terminated when the Go_loop is finished. Am I right?
It works to start the task one time but when the main-task tries to restart the working-task by calling the Start procedure, nothing happens.
Can someone please tell me which point I am missing.
A task and subprogram are somewhat related in that when the body is completed the construct ends, this is to say that the construct ends with it's appropriate end; in the case of a procedure control returns to the caller, in the case of a function the exception PROGRAM_ERROR is raised, and in the case of a task the controlling "thread" terminates.
What's happening in your particular problem, it seems, boils down to the following:
Package Example is
Task Type Message_Task is
Entry Execute;
End Message_Task;
End Example;
Package Body Example is
Task Body Message_Task is
Use Ada.Text_IO;
Begin
accept Execute do
Put_Line( "Rendezvous!" );
end Execute;
delay 0.2; -- Stub delay.
Put_Line( "Finishing Task." );
-- Task Ends Here.
End Message_Task;
End Example;
--...
Test : Example.Message_Task;
--...
Test.Execute;
-- Test.Execute can't be accepted here because it can only accept "Execute"
-- the one time, as per the body's definition.
The reason that this really is like your problem is because, likewise once you say "X.Start(1,2)" another call to Start doesn't reset the position of the task's execution back up to that accept.
If you wanted the task to "stay alive" for further processing you could do one of two options.
Option 1 -- set up a 'protocol':
Package Example is
Task Type Message_Task is
Entry Initialization;
Entry Execute;
Entry Quit;
End Message_Task;
End Example;
Package Body Example is
Task Body Message_Task is
Use Ada.Text_IO;
Has_quit : Boolean := False;
Begin
Main:
loop
select
accept Initialization do
null;
end Initialization;
accept Execute do
null;
end Execute;
or
accept Quit do
Has_Quit := True;
end Quit;
end select;
Exit Main when Has_Quit;
end loop Main;
End Message_Task;
End Example;
Option 2 -- Allow termination.
Package Example is
Task Type Message_Task is
Entry Initialization;
Entry Execute;
End Message_Task;
End Example;
Package Body Example is
Task Body Message_Task is
Use Ada.Text_IO;
Has_quit : Boolean := False;
Begin
accept Initialization do
null;
end Initialization;
Main:
loop
select
accept Execute do
null;
end Execute;
or
terminate;
end select;
end loop Main;
End Message_Task;
End Example;
The subtle difference is Option 2 gets rid of the Quit entry, allowing the task to 'rest' on the terminate alternative while Option 1 is more explicit in control (and required in some cases), but requiring that Initialization & Execute be called in pairs.
A task only runs until it reaches the end of its main sequence of statements (ignoring various technicalities).
If you want a task to do something, and then pause until it receives an external trigger, you should put a loop around the statements you have in the task body.

PLS-00103: Encountered the symbol “CREATE”

its showing error in 27 line create or replace function Buffalo
Declare
random_number number(4);
user_number number(4);
cow number(1);
buffaloes number(1):=0;
begin
random_number:=uniquetest(random_number);
/*random_number:=dbms_random.value(1000,9999);*/
dbms_output.put_line(random_number);
user_number:=&user_number;
while(user_number!=random_number)
loop
buffaloes:=Buffalo(user_number,random_number);
dbms_output.put_line('0'||'c'||buffaloes||'B');
buffaloes:=0;
user_number:=0;
user_number:=&user_number;
end loop;
end;
/*error in this line */
create or replace function Buffalo
(user_number in number,random_number in number)
return number
is
user_comparision number(1);
random_comparision number(1);
buffaloes number(1);
user_number1 number(4):=user_number;
random_number1 number(4):=random_number;
begin
while(user_number!=random_number)
loop
user_comparision:=user_number1 mod 10;
random_comparision:=random_number1 mod 10;
user_number1:=user_number1/10;
random_number1:=random_number1/10;
if(user_comparision = random_comparision)
then
buffaloes:=buffaloes+1;
end if;
end loop;
return buffaloes;
end;/
it is showing error in create statement. can anybody help me in solving this error.
Tell how to solve this create statement error.
it is showing error in create statement. can anybody help me in solving this error.
Tell how to solve this create statement error.
You should make 2 scripts of it. Currently you're starting off with a anonymous block, that is actually calling the function buffalo, while it hasn't been created yet.
Both the anonymous block and the function seem to be creating some infinite loop by the way,
So I'm not sure what you're trying to achieve here..
Without knowing the background of this problem it's impossible to give a solution.

Delphi Xe5 firedac Database locked error with SQLite database

I am trying to create a simple object to handle all my database related functions. I have a functions to return a dataset or to execute a command. Now when i call this from my program I am able to fetch records using Execute_Dataset and it works fine but when i do an changes and execute a command by calling Execute_Command i get an error "database is locked" when the commit transaction is called. I have tried everything that i could be it still happens. Can someone put some light into what i am doing wrong and how i can prevent this from happening.
function TConnectionManager.Execute_Dataset(const ASql: string; const AParams:
array of variant; out VDataset: TDataset; const ATrn_Name: string): Boolean;
var
lTrn: TFDTransaction;
lQry: TFDQuery;
begin
Result := True;
lTrn:= TFDTransaction.Create (Self);
try
lTrn.Connection := FConnection;
lTrn.StartTransaction;
lQry := TFDQuery.Create (Self);
lQry.Connection := FConnection;
lQry.Transaction := lTrn;
try
if Length (AParams) > 0
then lQry.Open (ASql, AParams)
else lQry.Open (ASql);
VDataset := lQry;
Result := True;
{ Commit transaction if started within the procedure }
lTrn.Commit;
except
on e:Exception
do begin
{ Rollback transaction if started within the procedure }
lTrn.Rollback;
lQry.DisposeOf;
//log
raise;
end;
end;
finally
lTrn.DisposeOf;
end;
end;
procedure TConnectionManager.Execute_Command(const ASql: string; const AParams:
array of variant; const ATrn_Name: string);
var
lTrn: TFDTransaction;
lQry: TFDQuery;
begin
lTrn:= TFDTransaction.Create (Self);
try
lTrn.Connection := FConnection;
lTrn.StartTransaction;
lQry := TFDQuery.Create (Self);
lQry.Connection := FConnection;
lQry.Transaction := lTrn;
try
{ Execute command }
if Length (AParams) > 0
then lQry.ExecSQL (ASql, AParams)
else lQry.ExecSQL (ASql);
{ Commit transaction if started within the procedure }
lTrn.Commit;
except
on e:Exception
do begin
{ Rollback transaction if started within the procedure }
lTrn.Rollback;
//log
raise;
end;
end;
finally
lQry.DisposeOf;
lTrn.DisposeOf;
end;
end;
Thanks
Try setting the Connection's properties SharedCache to 'False' and LockingMode to 'Normal'.
The default value for the connection's locking mode is 'exclusive' which can cause this problem. You can do this by right clicking on your Connection-Component (on the form) and choosing ConnectionEditor (I'm not quite sure, if that's the right English word for it, but it should be called something like that) and then setting these values.
Alternatively you can set these properties in sourcecode:
connection.Params.Add('SharedCache=False');
connection.Params.Add('LockingMode=Normal');
I'm not sure that this is the best way to solve this problem. There might be a better solution to this.
Because other connection exist. Check connection component in datamodule
MyFDConnection.Connected:=False;
Sharedcache = false is indeed the best way to solve this problem. The components already support designtime connections.

Resources