blob: 5f70a3f24283c2ec3306b4928ffb521681a728b7 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.testing.tests.flashback;
import java.util.*;
import java.math.*;
import org.eclipse.persistence.testing.models.employee.domain.*;
import org.eclipse.persistence.expressions.*;
import org.eclipse.persistence.history.*;
import org.eclipse.persistence.internal.history.*;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.platform.database.OraclePlatform;
import org.eclipse.persistence.queries.*;
import org.eclipse.persistence.mappings.querykeys.*;
import org.eclipse.persistence.sessions.DatabaseLogin;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.testing.framework.*;
/**
* <b>Purpose:</b> Tests all those unique scenarios involving
* Flashback that can not easily be generalized into a few
* generic cases.
*/
public class FlashbackUnitTestSuite extends TestSuite {
public FlashbackUnitTestSuite() {
setDescription("Fast unit testing of the flashback feature.");
}
protected Object getSystemChangeNumber() {
return ((FlashbackTestModel)getContainer()).getSystemChangeNumber();
}
protected Object getTimestamp() {
return FlashbackTestModel.getTimestamp();
}
protected AsOfClause getAsOfClause() {
return ((FlashbackTestModel)getContainer()).getAsOfClause();
}
public void _testAsOfStringLiteralTest() {
if (getSession().getProject().hasGenericHistorySupport()) {
return;
}
ValueReadQuery scnQuery = ((OraclePlatform)getSession().getPlatform()).getSystemChangeNumberQuery();
long sCNNow = ((BigDecimal)getSession().executeQuery(scnQuery)).longValue();
long testSCN = ((Number)getAsOfClause().getValue()).longValue();
ExpressionBuilder builder = new ExpressionBuilder(Employee.class);
AsOfClause sCNClause = new AsOfSCNClause(builder.value("" + sCNNow + " - " + (sCNNow - testSCN)));
ReadAllQuery query = new ReadAllQuery(Employee.class);
query.setAsOfClause(sCNClause);
query.dontMaintainCache();
Vector result = (Vector)getSession().executeQuery(query);
if (result.size() != 12) {
throw new TestErrorException("Expected 12 objects, read " + result.size());
}
}
public void _testAsOfParameterTest() {
ReadAllQuery query = new ReadAllQuery(Employee.class);
query.dontMaintainCache();
query.addArgument("ASOFCLAUSE");
Vector arguments = new Vector();
Object value = getAsOfClause().getValue();
arguments.add(value);
Expression parameterExp = query.getExpressionBuilder().getParameter("ASOFCLAUSE");
if (getAsOfClause().isAsOfSCNClause()) {
query.setAsOfClause(new AsOfSCNClause(parameterExp));
} else {
query.setAsOfClause(new AsOfClause(parameterExp));
}
Vector result = (Vector)getSession().executeQuery(query, arguments);
if (result.size() != 12) {
throw new TestErrorException("Expected 12 objects, read " + result.size());
}
/*arguments.set(0, new java.sql.Timestamp(System.currentTimeMillis()));
result = (Vector)getSession().executeQuery(query, arguments);
if (result.size() != 0) {
throw new TestErrorException("Expected 0 objects, read " + result.size());
}*/
}
public void _testAsOfCurrentTimeMillisTest() {
if (getSession().getProject().hasGenericHistorySupport()) {
return;
}
// Must get the current time from the database as a future or too long pass time will trigger an error.
long value = ((Date)getSession().executeQuery(new ValueReadQuery("Select SYSDATE from DUAL"))).getTime();
AsOfClause clause = new AsOfClause(value);
ReadAllQuery query = new ReadAllQuery(Employee.class);
query.dontMaintainCache();
query.setAsOfClause(clause);
Vector employees = (Vector)getSession().executeQuery(query);
// Since the current time gets mapped to the SCN +/- 5 minutes,
// the query of the current time may, or may contain employees,
// so cannot verify the result, just make sure the query worked.
}
public void _testAsOfCurrentTimeMillisParameterTest() {
if (getSession().getProject().hasGenericHistorySupport()) {
return;
}
// Must get the current time from the database as a future or too long pass time will trigger an error.
long value = ((Date)getSession().executeQuery(new ValueReadQuery("Select SYSDATE from DUAL"))).getTime();
AsOfClause clause = new AsOfClause((new ExpressionBuilder()).getParameter("TIME"));
ReadAllQuery query = new ReadAllQuery(Employee.class);
query.dontMaintainCache();
query.setAsOfClause(clause);
query.addArgument("TIME");
Vector arguments = new Vector();
arguments.add(value);
Vector employees = (Vector)getSession().executeQuery(query, arguments);
if (employees.size() != 0) {
throw new TestErrorException("Somehow the long value was not converted to a proper timestamp. Check the SQL against: " + value);
}
}
public void _testAsOfExpressionMathTest() {
if (!getAsOfClause().isAsOfSCNClause()) {
return;
}
ValueReadQuery scnQuery = ((OraclePlatform)getSession().getPlatform()).getSystemChangeNumberQuery();
long sCNNow = ((BigDecimal)getSession().executeQuery(scnQuery)).longValue();
long testSCN = ((BigDecimal)getAsOfClause().getValue()).longValue();
ExpressionBuilder builder = new ExpressionBuilder();
Expression expression = ExpressionMath.subtract(builder.value(sCNNow), builder.value(sCNNow - testSCN));
ReadAllQuery query = new ReadAllQuery(Employee.class);
query.setAsOfClause(new AsOfSCNClause(expression));
query.dontMaintainCache();
Vector result = (Vector)getSession().executeQuery(query);
if (result.size() != 12) {
throw new TestErrorException("Expected 12 objects, read " + result.size());
}
}
/**
* A query result can be cached normally if the builder is not as of
* a past time. However, if this condition is true but a a joined attribute
* as of a past time is added, what will happen?
* <p>What should happen is either an exception should be thrown, or the
* joined attribute should be read as of the current time. This test will find out
* which.
*/
public void _testCacheCorruptedByJoinedAttributeTest() {
// Try reading all projects now connected to all projects as of a past time.
try {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
ExpressionBuilder project = new ExpressionBuilder(LargeProject.class);
ExpressionBuilder oldProject = new ExpressionBuilder(LargeProject.class);
oldProject.asOf(getAsOfClause());
Expression criteria = project.equal(oldProject).and(oldProject.get("teamLeader").notNull());
ReadAllQuery query = new ReadAllQuery(LargeProject.class, criteria);
query.addJoinedAttribute(oldProject.get("teamLeader"));
Vector projects = (Vector)getSession().executeQuery(query);
for (int i = 0; i < projects.size(); i++) {
LargeProject next = (LargeProject)projects.get(i);
if (next.getTeamLeader() != null) {
throw new TestErrorException("The dynamic query must still return objects as they exist now, even if a joined attribute is added as of a past time.");
}
}
} finally {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
}
}
/**
* Tests corruption from joined attributes again.
* This test is simpler and more lethal than the previous one, for it cuts to
* the core of the problem. To tell if past rows are being returned, we can
* not just look at the builder.
*/
public void _testCacheCorruptedByJoinedAttribute2Test() {
// Try reading all projects now connected to all projects as of a past time.
try {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
ExpressionBuilder project = new ExpressionBuilder(LargeProject.class);
Expression criteria = project.get("teamLeader").asOf(getAsOfClause()).notNull();
ReadAllQuery query = new ReadAllQuery(LargeProject.class, criteria);
query.addJoinedAttribute("teamLeader");
Vector projects = (Vector)getSession().executeQuery(query);
for (int i = 0; i < projects.size(); i++) {
LargeProject next = (LargeProject)projects.get(i);
if (next.getTeamLeader() != null) {
throw new TestErrorException("The dynamic query must still return objects as they exist now, even if a joined attribute is added on an attribute read as of a past time.");
}
}
} finally {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
}
}
/**
* Tests corruption from joined attributes again.
* This test is simpler and more lethal than the previous one, for it cuts to
* the core of the problem. To tell if past rows are being returned, we can
* not just look at the builder.
*/
public void _testCacheCorruptedByBatchAttributeTest() {
// Try reading all projects now connected to all projects as of a past time.
try {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
ExpressionBuilder project = new ExpressionBuilder(LargeProject.class);
Expression criteria = project.get("teamLeader").asOf(getAsOfClause()).notNull();
ReadAllQuery query = new ReadAllQuery(LargeProject.class, criteria);
query.addBatchReadAttribute("teamLeader");
Vector projects = (Vector)getSession().executeQuery(query);
for (int i = 0; i < projects.size(); i++) {
LargeProject next = (LargeProject)projects.get(i);
if (next.getTeamLeader() != null) {
throw new TestErrorException("The dynamic query must still return objects as they exist now, even if a batched attribute is added on an attribute read as of a past time.");
}
}
} finally {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
}
}
/**
* This is testing for a condition that should never happen.
*/
public void _testCacheIsolationTest() {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
Session hs = getSession().acquireHistoricalSession(getAsOfClause());
try {
hs.readAllObjects(Employee.class);
Vector employees = ((AbstractSession)getSession()).getIdentityMapAccessorInstance().getAllFromIdentityMap(null, Employee.class, null);
if ((employees != null) && (employees.size() > 0)) {
throw new TestErrorException("" + employees.size() + " objects read in a HistoricalSession were cached in the global session.");
}
} finally {
hs.release();
}
}
/**
* This is testing for a condition that should never happen.
*/
public void _testCacheIsolationAcrossRelationshipsTest() {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
Session hs = getSession().acquireHistoricalSession(getAsOfClause());
try {
Vector pastEmployees = hs.readAllObjects(Employee.class);
for (int i = 0; i < pastEmployees.size(); i++) {
Employee employee = (Employee)pastEmployees.get(i);
employee.getProjects();
employee.getAddress();
employee.getPhoneNumbers();
}
Vector projects = ((AbstractSession)getSession()).getIdentityMapAccessorInstance().getAllFromIdentityMap(null, Project.class, null);
Vector addresses = ((AbstractSession)getSession()).getIdentityMapAccessorInstance().getAllFromIdentityMap(null, Address.class, null);
Vector phoneNumbers = ((AbstractSession)getSession()).getIdentityMapAccessorInstance().getAllFromIdentityMap(null, PhoneNumber.class, null);
if ((projects.size() > 0) || (addresses.size() > 0) || (phoneNumbers.size() > 0)) {
throw new TestErrorException("" + projects.size() + " projects, " + addresses.size() + " addresses, and " + phoneNumbers.size() + " phone numbers read in a HistoricalSession were cached in the global session.");
}
} finally {
hs.release();
}
}
public void _testCannotExecuteWriteInHistoricalSessionExceptionTest() {
Session hs = getSession().acquireHistoricalSession(getAsOfClause());
try {
((org.eclipse.persistence.internal.sessions.AbstractSession)hs).writeObject(new Employee());
throw new TestErrorException("Exception not thrown in executing a write in a TimeAwareSession.");
} catch (ValidationException ve) {
if (ve.getErrorCode() != QueryException.INVALID_QUERY_ON_HISTORICAL_SESSION) {
throw new TestErrorException("Wrong exception thrown.", ve);
}
} catch (TestErrorException tee) {
throw tee;
} catch (Exception e) {
throw new TestErrorException("Exception not thrown in executing a write in a Historical session. Instead triggered: ", e);
} finally {
hs.release();
}
}
public void _testDynamicQueryUsingParallelExpressionTest() {
// Try reading all projects now connected to all projects as of a past time.
try {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
ExpressionBuilder project = new ExpressionBuilder(LargeProject.class);
ExpressionBuilder oldProject = new ExpressionBuilder(LargeProject.class);
oldProject.asOf(getAsOfClause());
Expression criteria = project.equal(oldProject).and(oldProject.get("teamLeader").notNull());
Vector projects = getSession().readAllObjects(LargeProject.class, criteria);
if (projects.size() != 3) {
throw new TestErrorException("Expected to read 6 projects, instead read: " + projects.size());
}
for (int i = 0; i < projects.size(); i++) {
LargeProject next = (LargeProject)projects.get(i);
if (next.getTeamLeader() != null) {
throw new TestErrorException("The dynamic query must still return objects as they exist now.");
}
}
} finally {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
}
}
public void _testDynamicQueryUsingQueryKeyTest() {
// Try reading all projects now connected to all projects as of a past time.
try {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
ExpressionBuilder builder = new ExpressionBuilder(LargeProject.class);
Expression joinCriteria = builder.getField("PROJECT.PROJ_ID").equal(builder.getParameter("PROJECT.PROJ_ID"));
OneToOneQueryKey self = new OneToOneQueryKey();
self.setName("this");
self.setJoinCriteria(joinCriteria);
self.setReferenceClass(LargeProject.class);
getSession().getDescriptor(LargeProject.class).addQueryKey(self);
ExpressionBuilder project = new ExpressionBuilder(LargeProject.class);
Expression oldProject = project.get("this");
oldProject.asOf(getAsOfClause());
Expression criteria = oldProject.get("teamLeader").notNull();
Vector projects = getSession().readAllObjects(LargeProject.class, criteria);
if (projects.size() != 3) {
throw new TestErrorException("Expected to read 6 projects, instead read: " + projects.size());
}
for (int i = 0; i < projects.size(); i++) {
LargeProject next = (LargeProject)projects.get(i);
if (next.getTeamLeader() != null) {
throw new TestErrorException("The dynamic query must still return objects as they exist now.");
}
}
} finally {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
}
}
public void _testNoNestedHistoricalSessionsAllowedExceptionTest() {
Session hs = getSession().acquireHistoricalSession(getAsOfClause());
try {
Session nhs = hs.acquireHistoricalSession(getAsOfClause());
nhs.release();
throw new TestErrorException("Exception not thrown in acquiring a nested HistoricalSession.");
} catch (ValidationException ve) {
if (ve.getErrorCode() != ValidationException.CANNOT_ACQUIRE_HISTORICAL_SESSION) {
throw new TestErrorException("Wrong exception thrown.", ve);
}
} catch (TestErrorException tee) {
throw tee;
} catch (Exception e) {
throw new TestErrorException("Exception not thrown in reading past versions of objects into the global cache. Instead triggered: ", e);
} finally {
hs.release();
}
}
public void _testNoTransactionsInHistoricalSessionExceptionTest() {
HistoricalSession hs = (HistoricalSession)getSession().acquireHistoricalSession(getAsOfClause());
try {
hs.beginTransaction();
hs.rollbackTransaction();
throw new TestErrorException("Exception not thrown in beginning a transaction.");
} catch (ValidationException ve1) {
if (ve1.getErrorCode() != ValidationException.OPERATION_NOT_SUPPORTED) {
throw new TestErrorException("Wrong exception thrown.", ve1);
} else {
try {
hs.commitTransaction();
} catch (ValidationException ve2) {
if (ve2.getErrorCode() != ValidationException.OPERATION_NOT_SUPPORTED) {
throw new TestErrorException("Wrong exception thrown.", ve2);
} else {
try {
hs.rollbackTransaction();
} catch (ValidationException ve3) {
if (ve3.getErrorCode() != ValidationException.OPERATION_NOT_SUPPORTED) {
throw new TestErrorException("Wrong exception thrown.", ve3);
} else {
try {
hs.acquireUnitOfWork();
} catch (ValidationException ve4) {
if (ve4.getErrorCode() != ValidationException.OPERATION_NOT_SUPPORTED) {
throw new TestErrorException("Wrong exception thrown.", ve4);
}
}
}
}
}
}
}
} catch (TestErrorException tee) {
throw tee;
} catch (Exception e) {
throw new TestErrorException("Exception not thrown in reading past versions of objects into the global cache. Instead triggered: ", e);
} finally {
hs.release();
}
}
public void _testParameterBindingTest() {
ExpressionBuilder builder = new ExpressionBuilder();
Expression expression = builder.get("firstName").equal(builder.getParameter("FIRSTNAME"));
Vector arguments = new Vector();
arguments.add("Bob");
ReadObjectQuery query = new ReadObjectQuery(Employee.class);
query.setAsOfClause(getAsOfClause());
query.dontMaintainCache();
query.addArgument("FIRSTNAME");
Employee result = (Employee)getSession().executeQuery(query, arguments);
if (!result.getFirstName().equals("Bob")) {
throw new TestErrorException("Expected Bob, read " + result);
}
arguments.set(0, "Sarah");
result = (Employee)getSession().executeQuery(query, arguments);
if (!result.getFirstName().equals("Sarah")) {
throw new TestErrorException("Expected Sarah, read " + result);
}
}
/**
* Tests the entire point of a TimeAwareSession, which is to allow
* caching of read results, which in turn guarantees that circular references
* form a circle, not an infinite loop.
*/
public void _testSuccessfulCachingTest() {
getSession().getIdentityMapAccessor().initializeIdentityMaps();
Session hs = getSession().acquireHistoricalSession(getAsOfClause());
try {
Employee pastManager = (Employee)hs.readObject(Employee.class, new ExpressionBuilder().anyOf("managedEmployees").get("id").greaterThan(0));
Vector employees = pastManager.getManagedEmployees();
for (int i = 0; i < employees.size(); i++) {
Employee employee = (Employee)employees.get(i);
if (employee.getManager() != pastManager) {
throw new TestErrorException("Objects read in a HistoricalSession are not being cached properly by identity.");
}
}
} finally {
hs.release();
}
}
public void _testOuterJoinTest() {
internalOuterJoinTest(true);
}
public void _testOuterJoinTestWithoutAsOf() {
internalOuterJoinTest(false);
}
void internalOuterJoinTest(boolean withAsOf) {
ReadAllQuery query = new ReadAllQuery(LargeProject.class);
ExpressionBuilder builder = query.getExpressionBuilder();
Expression expression = builder.getAllowingNull("teamLeader").get("firstName").equal("Sarah");
expression = expression.or(builder.get("name").equal("TOPEmployee Management"));
query.setSelectionCriteria(expression);
query.dontMaintainCache();
if(withAsOf) {
query.setAsOfClause(getAsOfClause());
}
Vector result = (Vector)getSession().executeQuery(query);
getSession().getIdentityMapAccessor().initializeIdentityMaps();
int expectedSize = (withAsOf ? 2 : 1);
if ((result == null) || (result.size() != expectedSize)) {
throw new TestErrorException("Expected "+expectedSize+" objects, read " + result.size());
}
}
public void _testHistoricalQueriesMustPreserveGlobalCacheExceptionTest() {
ReadAllQuery query = new ReadAllQuery(Employee.class);
query.setSelectionCriteria(query.getExpressionBuilder().get("firstName").equal("Bob"));
query.setAsOfClause(new AsOfSCNClause(Long.valueOf(0)));
try {
getSession().executeQuery(query);
getSession().getIdentityMapAccessor().initializeIdentityMaps();
throw new TestErrorException("Exception not thrown in reading past versions of objects into the global cache.");
} catch (QueryException qe) {
if (qe.getErrorCode() != QueryException.HISTORICAL_QUERIES_MUST_PRESERVE_GLOBAL_CACHE) {
throw new TestErrorException("Wrong exception thrown.", qe);
}
} catch (TestErrorException tee) {
throw tee;
} catch (Exception e) {
throw new TestErrorException("Exception not thrown in reading past versions of objects into the global cache. Instead triggered: ", e);
}
}
public void _testHistoricalQueriesOnlySupportedOnOracleExceptionTest() {
boolean genericHistory = getSession().getProject().hasGenericHistorySupport();
ReadAllQuery query = new ReadAllQuery(Employee.class);
query.setSelectionCriteria(query.getExpressionBuilder().get("firstName").equal("Bob"));
query.setAsOfClause(getAsOfClause());
query.dontMaintainCache();
DatabaseLogin oldLogin = getSession().getLogin();
DatabaseLogin newLogin = (DatabaseLogin)oldLogin.clone();
newLogin.useAccess();
try {
getAbstractSession().setLogin(newLogin);
getSession().executeQuery(query);
getSession().getIdentityMapAccessor().initializeIdentityMaps();
if (!genericHistory) {
throw new TestErrorException("Exception not thrown in reading past versions of objects outside of Oracle.");
}
} catch (QueryException qe) {
if (qe.getErrorCode() != QueryException.HISTORICAL_QUERIES_ONLY_SUPPORTED_ON_ORACLE) {
throw new TestErrorException("Wrong exception thrown.", qe);
}
} catch (TestErrorException tee) {
throw tee;
} catch (Exception e) {
throw new TestErrorException("Exception not thrown in reading past versions of objects outside of Oracle. Instead triggered: " + e.toString(), e);
} finally {
getAbstractSession().setLogin(oldLogin);
}
}
public void _testHistoricalSessionOnlySupportedOnOracleExceptionTest() {
if (getSession().getProject().hasGenericHistorySupport()) {
return;
}
DatabaseLogin oldLogin = getSession().getLogin();
DatabaseLogin newLogin = (DatabaseLogin)oldLogin.clone();
newLogin.useAccess();
try {
getAbstractSession().setLogin(newLogin);
Session hs = getSession().acquireHistoricalSession(getAsOfClause());
hs.release();
throw new TestErrorException("Exception not thrown in acquiring a HistoricalSession outside of Oracle.");
} catch (ValidationException ve) {
if (ve.getErrorCode() != ValidationException.HISTORICAL_SESSION_ONLY_SUPPORTED_ON_ORACLE) {
throw new TestErrorException("Wrong exception thrown.", ve);
}
} catch (TestErrorException tee) {
throw tee;
} catch (Exception e) {
throw new TestErrorException("Exception not thrown in reading past versions of objects into the global cache. Instead triggered: ", e);
} finally {
getAbstractSession().setLogin(oldLogin);
}
}
@Override
public void addTests() {
addTest(new UnitTestCase("AsOfCurrentTimeMillisTest"));
// broken addTest(new UnitTestCase("AsOfCurrentTimeMillisParameterTest"));
addTest(new UnitTestCase("AsOfExpressionMathTest"));
addTest(new UnitTestCase("AsOfParameterTest"));
addTest(new UnitTestCase("AsOfStringLiteralTest"));
// broken addTest(new UnitTestCase("CacheCorruptedByJoinedAttributeTest"));
//broken addTest(new UnitTestCase("CacheCorruptedByJoinedAttribute2Test"));
addTest(new UnitTestCase("CacheCorruptedByBatchAttributeTest"));
addTest(new UnitTestCase("CacheIsolationTest"));
addTest(new UnitTestCase("CacheIsolationAcrossRelationshipsTest"));
addTest(new UnitTestCase("CannotExecuteWriteInHistoricalSessionExceptionTest"));
addTest(new UnitTestCase("DynamicQueryUsingQueryKeyTest"));
addTest(new UnitTestCase("DynamicQueryUsingParallelExpressionTest"));
addTest(new UnitTestCase("NoNestedHistoricalSessionsAllowedExceptionTest"));
addTest(new UnitTestCase("NoTransactionsInHistoricalSessionExceptionTest"));
addTest(new UnitTestCase("SuccessfulCachingTest"));
addTest(new UnitTestCase("OuterJoinTest"));
addTest(new UnitTestCase("OuterJoinTestWithoutAsOf"));
addTest(new UnitTestCase("HistoricalQueriesMustPreserveGlobalCacheExceptionTest"));
addTest(new UnitTestCase("HistoricalQueriesOnlySupportedOnOracleExceptionTest"));
addTest(new UnitTestCase("HistoricalSessionOnlySupportedOnOracleExceptionTest"));
}
}