diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetHandler.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetHandler.java index e49b466834245b1f295b4d0465a97cc63dea47d7..a5c1cd2141907cb71f9fc0a886fc23c22f114e92 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetHandler.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetHandler.java @@ -263,7 +263,7 @@ public class JythonTopLevelDataSetHandler<T extends DataSetInformation> extends PyFunction function = tryJythonFunction(interpreter, POST_STORAGE_FUNCTION_NAME); if (null != function) { - invokeTransactionFunctionWithContext(function, transaction); + invokeTransactionFunctionWithContext(function, service, transaction); } else { function = tryJythonFunction(interpreter, COMMIT_TRANSACTION_FUNCTION_NAME); @@ -281,7 +281,7 @@ public class JythonTopLevelDataSetHandler<T extends DataSetInformation> extends PyFunction function = tryJythonFunction(interpreter, PRE_REGISTRATION_FUNCTION_NAME); if (null != function) { - invokeTransactionFunctionWithContext(function, transaction); + invokeTransactionFunctionWithContext(function, service, transaction); } } @@ -292,7 +292,7 @@ public class JythonTopLevelDataSetHandler<T extends DataSetInformation> extends PyFunction function = tryJythonFunction(interpreter, POST_REGISTRATION_FUNCTION_NAME); if (null != function) { - invokeTransactionFunctionWithContext(function, transaction); + invokeTransactionFunctionWithContext(function, service, transaction); } } @@ -368,7 +368,7 @@ public class JythonTopLevelDataSetHandler<T extends DataSetInformation> extends * Pulled out as a separate method so tests can hook in. */ protected void invokeTransactionFunctionWithContext(PyFunction function, - DataSetRegistrationTransaction<T> transaction) + DataSetRegistrationService<T> service, DataSetRegistrationTransaction<T> transaction) { function.__call__(Py.java2py(transaction), Py.java2py(null)); } diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/AbstractJythonDataSetHandlerTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/AbstractJythonDataSetHandlerTest.java index cb40a6555dfe1d158496d1c6abd098c48d500281..bd88ade99516220093b69ed49daf02fdfe9d85f6 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/AbstractJythonDataSetHandlerTest.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/AbstractJythonDataSetHandlerTest.java @@ -396,6 +396,12 @@ public abstract class AbstractJythonDataSetHandlerTest extends AbstractFileSyste protected boolean didSecondaryTransactionErrorNotificationHappen = false; + protected boolean didPostRegistrationFunctionRunHappen = false; + + protected boolean didPreRegistrationFunctionRunHappen = false; + + protected boolean didPostStorageFunctionRunHappen = false; + public TestingDataSetHandler(TopLevelDataSetRegistratorGlobalState globalState, boolean shouldRegistrationFail, boolean shouldReThrowRollbackException) { @@ -491,7 +497,7 @@ public abstract class AbstractJythonDataSetHandlerTest extends AbstractFileSyste ((JythonDataSetRegistrationService<DataSetInformation>) service) .getInterpreter(); didRollbackTransactionFunctionRunHappen = - interpreter.get("didTransactionRollbackHappen", Boolean.class); + readBoolean(interpreter, "didTransactionRollbackHappen"); } @Override @@ -505,9 +511,38 @@ public abstract class AbstractJythonDataSetHandlerTest extends AbstractFileSyste ((JythonDataSetRegistrationService<DataSetInformation>) service) .getInterpreter(); didCommitTransactionFunctionRunHappen = - interpreter.get("didTransactionCommitHappen", Boolean.class); + readBoolean(interpreter, "didTransactionCommitHappen"); } + @Override + protected void invokeTransactionFunctionWithContext(PyFunction function, + DataSetRegistrationService<DataSetInformation> service, + DataSetRegistrationTransaction<DataSetInformation> transaction) + { + super.invokeTransactionFunctionWithContext(function, service, transaction); + PythonInterpreter interpreter = + ((JythonDataSetRegistrationService<DataSetInformation>) service) + .getInterpreter(); + + didPreRegistrationFunctionRunHappen = + readBoolean(interpreter,"didPreRegistrationFunctionRunHappen"); + + didPostRegistrationFunctionRunHappen = + readBoolean(interpreter, "didPostRegistrationFunctionRunHappen"); + + didPostStorageFunctionRunHappen = + readBoolean(interpreter,"didPostStorageFunctionRunHappen"); + + } + + //reads boolean or false if null from interpreter + private boolean readBoolean(PythonInterpreter interpreter, String variable) + { + Boolean retVal = interpreter.get(variable, Boolean.class); + if (retVal == null) return false; + return retVal; + } + @Override public void didEncounterSecondaryTransactionErrors( DataSetRegistrationService<DataSetInformation> service, diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetRegistratorTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetRegistratorTest.java index 4818ee027f81833f41d2a09950c5700057ce8f0f..29b620f3bfa2b009887c67d68684e707d5d369d1 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetRegistratorTest.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/JythonTopLevelDataSetRegistratorTest.java @@ -52,6 +52,7 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.utils.DatasetLocationUtil; import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria; import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria.MatchClause; import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria.MatchClauseAttribute; +import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.ShouldFlattenCollections; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ContainerDataSet; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetType; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityProperty; @@ -204,6 +205,18 @@ public class JythonTopLevelDataSetRegistratorTest extends AbstractJythonDataSetH testCase.shouldValidationFail = true; testCases.add(testCase); + testCase = + new TestCaseParameters( + "The simple validation without post_storage function defined."); + testCase.dropboxScriptPath = "simple-transaction-without-post-storage.py"; + testCase.postStorageFunctionNotDefinedInADropbox = true; + testCases.add(testCase); + + testCase = new TestCaseParameters("Dataset file not found."); + testCase.dropboxScriptPath = "file-not-found.py"; + testCase.shouldNotFindDataSetFile = true; + testCases.add(testCase); + // TODO: Add more scenarios: // - Test move to error // - Test moving of the original file in case of validation error @@ -230,19 +243,52 @@ public class JythonTopLevelDataSetRegistratorTest extends AbstractJythonDataSetH */ private static class TestCaseParameters { - + /** + * short description of the test. Will be presented in the test results view + */ protected String title; + /** + * The dropbox script file that should be used for this test case + */ + protected String dropboxScriptPath = "simple-transaction.py"; + + /** + * Specifies what properties should be overriden for this test case. + */ protected HashMap<String, String> overrideProperties; + /** + * Describe what should happen with incoming data after execution of this test case. + */ protected String incomingDataSetAfterRegistration = "deleted"; + /** + * Specifies the custom creator of datasets instead of createDataWithOneSubDataSet. + */ protected IDelegatedAction createDataSetDelegate = null; + /** + * True if the registration of metadata should fail + */ protected boolean shouldRegistrationFail = false; + /** + * True if assertValidDataSet method should return validation error on dataset + */ protected boolean shouldValidationFail = false; + /** + * True if the dropbox script should not find the specified datasetFile + */ + protected boolean shouldNotFindDataSetFile = false; + + /** + * True if commit_transaction function is defined in a jython dropbox script file, and + * post_storage function is not. + */ + protected boolean postStorageFunctionNotDefinedInADropbox = false; + private TestCaseParameters(String title) { this.title = title; @@ -259,9 +305,11 @@ public class JythonTopLevelDataSetRegistratorTest extends AbstractJythonDataSetH public void testSimpleTransaction(final TestCaseParameters testCase) { setUpHomeDataBaseExpectations(); + Properties properties = - createThreadPropertiesRelativeToScriptsFolder("simple-transaction.py", + createThreadPropertiesRelativeToScriptsFolder(testCase.dropboxScriptPath, testCase.overrideProperties); + if (testCase.shouldRegistrationFail || testCase.shouldValidationFail) { createHandler(properties, false, false); @@ -289,21 +337,34 @@ public class JythonTopLevelDataSetRegistratorTest extends AbstractJythonDataSetH one(openBisService).createDataSetCode(); will(returnValue(DATA_SET_CODE)); - atLeast(1).of(openBisService).tryToGetExperiment( - new ExperimentIdentifierFactory(experiment.getIdentifier()) - .createIdentifier()); - will(returnValue(experiment)); - one(dataSetValidator).assertValidDataSet(DATA_SET_TYPE, - new File(new File(stagingDirectory, DATA_SET_CODE), "sub_data_set_1")); + if (testCase.shouldNotFindDataSetFile) + { + broken = true; + } - if (testCase.shouldValidationFail) + if (false == broken) { - Exception innerException = new Exception(); - will(throwException(new UserFailureException("Data set of type '" - + DATA_SET_CODE + "' is invalid ", innerException))); + atLeast(1).of(openBisService).tryToGetExperiment( + new ExperimentIdentifierFactory(experiment.getIdentifier()) + .createIdentifier()); + will(returnValue(experiment)); + } - broken = true; + if (false == broken) + { + one(dataSetValidator).assertValidDataSet( + DATA_SET_TYPE, + new File(new File(stagingDirectory, DATA_SET_CODE), + "sub_data_set_1")); + + if (testCase.shouldValidationFail) + { + Exception innerException = new Exception(); + will(throwException(new UserFailureException("Data set of type '" + + DATA_SET_CODE + "' is invalid ", innerException))); + broken = true; + } } if (false == broken) @@ -330,13 +391,31 @@ public class JythonTopLevelDataSetRegistratorTest extends AbstractJythonDataSetH } }); - handler.handle(markerFile); + if (testCase.shouldNotFindDataSetFile) + { + try + { + handler.handle(markerFile); + fail("Expected a FileNotFound exception."); + } catch (PyException pyException) + { + IOExceptionUnchecked tunnel = (IOExceptionUnchecked) pyException.getCause(); + FileNotFoundException ex = (FileNotFoundException) tunnel.getCause(); + assertTrue(ex.getMessage().startsWith("Neither '/non/existent/path' nor '")); + } + context.assertIsSatisfied(); + return; + } else + { + handler.handle(markerFile); + } + checkInitialDirAfterRegistration(testCase.incomingDataSetAfterRegistration); - if (!testCase.shouldValidationFail) + if (false == testCase.shouldValidationFail) { - // the incoming dir in storage processor is created at the beginning of transaction - so - // after the successful registration + // the incoming dir in storage processor is created at the beginning of transaction + // so after the successful validation assertEquals(1, MockStorageProcessor.instance.incomingDirs.size()); } @@ -345,23 +424,15 @@ public class JythonTopLevelDataSetRegistratorTest extends AbstractJythonDataSetH assertEquals(expectedCommitCount, MockStorageProcessor.instance.calledCommitCount); + assertJythonHooksExecuted(testCase); + if (testCase.shouldValidationFail) { } else if (testCase.shouldRegistrationFail) { assertEquals("[]", Arrays.asList(stagingDirectory.list()).toString()); - - TestingDataSetHandler theHandler = (TestingDataSetHandler) handler; - assertFalse(theHandler.didRollbackDataSetRegistrationFunctionRun); - assertFalse(theHandler.didRollbackServiceFunctionRun); - assertTrue(theHandler.didTransactionRollbackHappen); - assertTrue(theHandler.didRollbackTransactionFunctionRunHappen); } else { - TestingDataSetHandler theHandler = (TestingDataSetHandler) handler; - assertTrue(theHandler.didCommitTransactionFunctionRunHappen); - assertFalse(theHandler.didRollbackTransactionFunctionRunHappen); - assertEquals(1, MockStorageProcessor.instance.incomingDirs.size()); assertEquals(1, atomicatOperationDetails.recordedObject().getDataSetRegistrations() .size()); @@ -395,34 +466,48 @@ public class JythonTopLevelDataSetRegistratorTest extends AbstractJythonDataSetH context.assertIsSatisfied(); } - @Test - public void testFileNotFound() + private void assertJythonHooksExecuted(final TestCaseParameters testCase) { - setUpHomeDataBaseExpectations(); - Properties properties = createThreadPropertiesRelativeToScriptsFolder("file-not-found.py"); - createHandler(properties, false, true); - createData(); - - context.checking(new Expectations() - { - { - one(openBisService).createDataSetCode(); - will(returnValue(DATA_SET_CODE)); - } - }); + TestingDataSetHandler theHandler = (TestingDataSetHandler) handler; - try + if (testCase.shouldValidationFail) { - handler.handle(markerFile); - fail("Expected a FileNotFound exception."); - } catch (PyException pyException) + } else if (testCase.shouldRegistrationFail) { - IOExceptionUnchecked tunnel = (IOExceptionUnchecked) pyException.getCause(); - FileNotFoundException ex = (FileNotFoundException) tunnel.getCause(); - assertTrue(ex.getMessage().startsWith("Neither '/non/existent/path' nor '")); + assertFalse(theHandler.didRollbackDataSetRegistrationFunctionRun); + assertFalse(theHandler.didRollbackServiceFunctionRun); + assertTrue(theHandler.didTransactionRollbackHappen); + assertTrue(theHandler.didRollbackTransactionFunctionRunHappen); + + assertTrue(theHandler.didPreRegistrationFunctionRunHappen); + assertFalse(theHandler.didPostRegistrationFunctionRunHappen); + + assertFalse(theHandler.didCommitTransactionFunctionRunHappen); + assertFalse(theHandler.didPostStorageFunctionRunHappen); + + } else + { + assertFalse(theHandler.didRollbackTransactionFunctionRunHappen); + + assertTrue(theHandler.didPreRegistrationFunctionRunHappen); + assertTrue(theHandler.didPostRegistrationFunctionRunHappen); + + if (testCase.postStorageFunctionNotDefinedInADropbox) + { + assertTrue(theHandler.didCommitTransactionFunctionRunHappen); + assertFalse(theHandler.didPostStorageFunctionRunHappen); + } else + { + assertFalse(theHandler.didCommitTransactionFunctionRunHappen); + assertTrue(theHandler.didPostStorageFunctionRunHappen); + } } + } + + @Test + public void testFileNotFound() + { - context.assertIsSatisfied(); } @Test diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/simple-transaction-without-post-storage.py b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/simple-transaction-without-post-storage.py new file mode 100644 index 0000000000000000000000000000000000000000..fe7671fab6ca59eba33b7b5b4f64392200599d3a --- /dev/null +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/simple-transaction-without-post-storage.py @@ -0,0 +1,21 @@ +def rollback_transaction(service, transaction, algorithmRunner, throwable): + global didTransactionRollbackHappen + didTransactionRollbackHappen = True + +def commit_transaction(service, transaction): + global didTransactionCommitHappen + didTransactionCommitHappen = True + +def pre_metadata_registration(transaction, context): + global didPreRegistrationFunctionRunHappen + didPreRegistrationFunctionRunHappen = True + +def post_metadata_registration(transaction, context): + global didPostRegistrationFunctionRunHappen + didPostRegistrationFunctionRunHappen = True + +transaction = service.transaction(incoming, factory) +dataSet = transaction.createNewDataSet() +transaction.moveFile(incoming.getPath() + '/sub_data_set_1', dataSet) +dataSet.setDataSetType('O1') +dataSet.setExperiment(transaction.getExperiment('/SPACE/PROJECT/EXP')) diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/simple-transaction.py b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/simple-transaction.py index d9227f3f0e8e1d43d367fdc5ca7382cabd08a377..06f747c519f6dd9e6041126f148c961b42173feb 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/simple-transaction.py +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/registrator/simple-transaction.py @@ -1,11 +1,23 @@ def rollback_transaction(service, transaction, algorithmRunner, throwable): - global didTransactionRollbackHappen - didTransactionRollbackHappen = True + global didTransactionRollbackHappen + didTransactionRollbackHappen = True def commit_transaction(service, transaction): - global didTransactionCommitHappen - didTransactionCommitHappen = True - + global didTransactionCommitHappen + didTransactionCommitHappen = True + +def post_storage(transaction, context): + global didPostStorageFunctionRunHappen + didPostStorageFunctionRunHappen = True + +def pre_metadata_registration(transaction, context): + global didPreRegistrationFunctionRunHappen + didPreRegistrationFunctionRunHappen = True + +def post_metadata_registration(transaction, context): + global didPostRegistrationFunctionRunHappen + didPostRegistrationFunctionRunHappen = True + transaction = service.transaction(incoming, factory) dataSet = transaction.createNewDataSet() transaction.moveFile(incoming.getPath() + '/sub_data_set_1', dataSet)