diff --git a/frontend/javascripts/test/sagas/volumetracing/bucket_eviction_expect_failure.spec.js b/frontend/javascripts/test/sagas/volumetracing/bucket_eviction_expect_failure.spec.js new file mode 100644 index 00000000000..4df9f2d832a --- /dev/null +++ b/frontend/javascripts/test/sagas/volumetracing/bucket_eviction_expect_failure.spec.js @@ -0,0 +1,49 @@ +// @flow +import test from "ava"; +import mockRequire from "mock-require"; +import { waitForCondition } from "libs/utils"; + +import "test/sagas/saga_integration.mock"; +import { __setupOxalis, createBucketResponseFunction } from "test/helpers/apiHelpers"; +import { restartSagaAction, wkReadyAction } from "oxalis/model/actions/actions"; +import Store from "oxalis/store"; +import { hasRootSagaCrashed } from "oxalis/model/sagas/root_saga"; + +import { testLabelingManyBuckets } from "./bucket_eviction_helper"; + +const { discardSaveQueuesAction } = mockRequire.reRequire("oxalis/model/actions/save_actions"); + +test.beforeEach(async t => { + // Setup oxalis, this will execute model.fetch(...) and initialize the store with the tracing, etc. + Store.dispatch(restartSagaAction()); + Store.dispatch(discardSaveQueuesAction()); + + await __setupOxalis(t, "volume"); + + // Dispatch the wkReadyAction, so the sagas are started + Store.dispatch(wkReadyAction()); +}); + +test.serial( + "Brushing/Tracing should crash when too many buckets are labeled at once without saving inbetween", + async t => { + await t.context.api.tracing.save(); + + t.context.mocks.Request.sendJSONReceiveArraybufferWithHeaders = createBucketResponseFunction( + Uint16Array, + 0, + 0, + ); + // webKnossos will start to evict buckets forcefully if too many are dirty at the same time. + // This is not ideal, but usually handled by the fact that buckets are regularly saved to the + // backend and then marked as not dirty. + // This test provokes that webKnossos crashes (a hard crash is only done during testing; in dev/prod + // a soft warning is emitted via the devtools). + // The corresponding sibling test checks that saving inbetween does not make webKnossos crash. + t.plan(2); + t.false(hasRootSagaCrashed()); + const failedSagaPromise = waitForCondition(hasRootSagaCrashed, 500); + await Promise.race([testLabelingManyBuckets(t, false), failedSagaPromise]); + t.true(hasRootSagaCrashed()); + }, +); diff --git a/frontend/javascripts/test/sagas/volumetracing/bucket_eviction_expect_success.spec.js b/frontend/javascripts/test/sagas/volumetracing/bucket_eviction_expect_success.spec.js new file mode 100644 index 00000000000..620192c019e --- /dev/null +++ b/frontend/javascripts/test/sagas/volumetracing/bucket_eviction_expect_success.spec.js @@ -0,0 +1,43 @@ +// @flow +import test from "ava"; +import mockRequire from "mock-require"; +import { waitForCondition } from "libs/utils"; + +import "test/sagas/saga_integration.mock"; +import { __setupOxalis, createBucketResponseFunction } from "test/helpers/apiHelpers"; +import { restartSagaAction, wkReadyAction } from "oxalis/model/actions/actions"; +import Store from "oxalis/store"; +import { hasRootSagaCrashed } from "oxalis/model/sagas/root_saga"; + +import { testLabelingManyBuckets } from "./bucket_eviction_helper"; + +const { discardSaveQueuesAction } = mockRequire.reRequire("oxalis/model/actions/save_actions"); + +test.beforeEach(async t => { + // Setup oxalis, this will execute model.fetch(...) and initialize the store with the tracing, etc. + Store.dispatch(restartSagaAction()); + Store.dispatch(discardSaveQueuesAction()); + + await __setupOxalis(t, "volume"); + + // Dispatch the wkReadyAction, so the sagas are started + Store.dispatch(wkReadyAction()); +}); + +test.serial( + "Brushing/Tracing should not crash when too many buckets are labeled at once with saving inbetween", + async t => { + await t.context.api.tracing.save(); + + t.context.mocks.Request.sendJSONReceiveArraybufferWithHeaders = createBucketResponseFunction( + Uint16Array, + 0, + 0, + ); + t.plan(2); + t.false(hasRootSagaCrashed()); + const failedSagaPromise = waitForCondition(hasRootSagaCrashed, 500); + await Promise.race([testLabelingManyBuckets(t, true), failedSagaPromise]); + t.false(hasRootSagaCrashed()); + }, +); diff --git a/frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.js b/frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.js new file mode 100644 index 00000000000..73ef43df7c9 --- /dev/null +++ b/frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.js @@ -0,0 +1,76 @@ +// @flow +import _ from "lodash"; +import mockRequire from "mock-require"; + +import "test/sagas/saga_integration.mock"; +import { createBucketResponseFunction } from "test/helpers/apiHelpers"; +import Store from "oxalis/store"; + +import { OrthoViews, AnnotationToolEnum } from "oxalis/constants"; +import { updateUserSettingAction } from "oxalis/model/actions/settings_actions"; + +const { setToolAction } = mockRequire.reRequire("oxalis/model/actions/ui_actions"); +const { setPositionAction } = mockRequire.reRequire("oxalis/model/actions/flycam_actions"); + +const { + setActiveCellAction, + addToLayerAction, + startEditingAction, + finishEditingAction, +} = mockRequire.reRequire("oxalis/model/actions/volumetracing_actions"); + +export async function testLabelingManyBuckets(t: any, saveInbetween: boolean) { + // We set MAXIMUM_BUCKET_COUNT to 150 and then label 199 = 75 (mag1) + 124 (downsampled) buckets in total. + // In between, we will save the data which allows the buckets of the first batch to be GC'ed. + // Therefore, saving the buckets of the second batch should not cause any problems. + t.context.model.getCubeByLayerName("segmentation").MAXIMUM_BUCKET_COUNT = 150; + + const oldCellId = 11; + const brushSize = 10; + const newCellId = 2; + + t.context.mocks.Request.sendJSONReceiveArraybufferWithHeaders = createBucketResponseFunction( + Uint16Array, + oldCellId, + 500, + ); + // Reload buckets which might have already been loaded before swapping the sendJSONReceiveArraybufferWithHeaders + // function. + await t.context.api.data.reloadAllBuckets(); + + // Prepare to paint into the center of 50 buckets. + const paintPositions1 = _.range(0, 50).map(idx => [32 * idx + 16, 32 * idx + 16, 32 * idx + 16]); + // Prepare to paint into the center of 50 other buckets. + const paintPositions2 = _.range(50, 100).map(idx => [ + 32 * idx + 16, + 32 * idx + 16, + 32 * idx + 16, + ]); + + Store.dispatch(updateUserSettingAction("brushSize", brushSize)); + Store.dispatch(setToolAction(AnnotationToolEnum.BRUSH)); + Store.dispatch(setActiveCellAction(newCellId)); + + for (const paintPosition of paintPositions1) { + Store.dispatch(setPositionAction(paintPosition)); + Store.dispatch(startEditingAction(paintPosition, OrthoViews.PLANE_XY)); + Store.dispatch(addToLayerAction(paintPosition)); + Store.dispatch(finishEditingAction()); + } + + console.log("saveInbetween", saveInbetween); + if (saveInbetween) { + await t.context.api.tracing.save(); + } + + for (const paintPosition of paintPositions2) { + Store.dispatch(setPositionAction(paintPosition)); + Store.dispatch(startEditingAction(paintPosition, OrthoViews.PLANE_XY)); + Store.dispatch(addToLayerAction(paintPosition)); + Store.dispatch(finishEditingAction()); + } + + await t.context.api.tracing.save(); +} + +export default {}; diff --git a/frontend/javascripts/test/sagas/volumetracing_saga.mock.js b/frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.mock.js similarity index 100% rename from frontend/javascripts/test/sagas/volumetracing_saga.mock.js rename to frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.mock.js diff --git a/frontend/javascripts/test/sagas/volumetracing_saga.spec.js b/frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.js similarity index 97% rename from frontend/javascripts/test/sagas/volumetracing_saga.spec.js rename to frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.js index c09ed34387b..453c587cc75 100644 --- a/frontend/javascripts/test/sagas/volumetracing_saga.spec.js +++ b/frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.js @@ -1,5 +1,5 @@ // @flow -import "test/sagas/volumetracing_saga.mock.js"; +import "test/sagas/volumetracing/volumetracing_saga.mock"; import sinon from "sinon"; import { take, put, call } from "redux-saga/effects"; @@ -20,8 +20,8 @@ import mockRequire from "mock-require"; import test from "ava"; import type { VolumeTracing } from "oxalis/store"; -import { expectValueDeepEqual, execCall } from "../helpers/sagaHelpers"; -import { withoutUpdateTracing } from "../helpers/saveHelpers"; +import { expectValueDeepEqual, execCall } from "test/helpers/sagaHelpers"; +import { withoutUpdateTracing } from "test/helpers/saveHelpers"; mockRequire("app", { currentUser: { firstName: "SCM", lastName: "Boy" } }); mockRequire("oxalis/model/sagas/root_saga", function*() { diff --git a/frontend/javascripts/test/sagas/volumetracing_saga_integration.spec.js b/frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration.spec.js similarity index 83% rename from frontend/javascripts/test/sagas/volumetracing_saga_integration.spec.js rename to frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration.spec.js index 58dd8590332..993c7a338f5 100644 --- a/frontend/javascripts/test/sagas/volumetracing_saga_integration.spec.js +++ b/frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration.spec.js @@ -1,10 +1,8 @@ // @flow /* eslint-disable no-await-in-loop */ -import mockRequire from "mock-require"; import test from "ava"; -import { waitForCondition } from "libs/utils"; -import _ from "lodash"; +import mockRequire from "mock-require"; import "test/sagas/saga_integration.mock"; import { @@ -489,98 +487,3 @@ test.serial("Brushing/Tracing with undo (II)", async t => { t.is(await t.context.api.data.getDataValue("segmentation", [1, 0, 0]), newCellId + 1); t.is(await t.context.api.data.getDataValue("segmentation", [5, 0, 0]), oldCellId); }); - -test.serial( - "Brushing/Tracing should crash when too many buckets are labeled at once without saving inbetween", - async t => { - await t.context.api.tracing.save(); - - t.context.mocks.Request.sendJSONReceiveArraybufferWithHeaders = createBucketResponseFunction( - Uint16Array, - 0, - 0, - ); - // webKnossos will start to evict buckets forcefully if too many are dirty at the same time. - // This is not ideal, but usually handled by the fact that buckets are regularly saved to the - // backend and then marked as not dirty. - // This test provokes that webKnossos crashes (a hard crash is only done during testing; in dev/prod - // a soft warning is emitted via the devtools). - // The corresponding sibling test checks that saving inbetween does not make webKnossos crash. - t.plan(2); - t.false(hasRootSagaCrashed()); - const failedSagaPromise = waitForCondition(hasRootSagaCrashed, 500); - await Promise.race([testLabelingManyBuckets(t, false), failedSagaPromise]); - t.true(hasRootSagaCrashed()); - }, -); - -test.serial( - "Brushing/Tracing should not crash when too many buckets are labeled at once with saving inbetween", - async t => { - await t.context.api.tracing.save(); - - t.context.mocks.Request.sendJSONReceiveArraybufferWithHeaders = createBucketResponseFunction( - Uint16Array, - 0, - 0, - ); - t.plan(2); - t.false(hasRootSagaCrashed()); - const failedSagaPromise = waitForCondition(hasRootSagaCrashed, 500); - await Promise.race([testLabelingManyBuckets(t, true), failedSagaPromise]); - t.false(hasRootSagaCrashed()); - }, -); - -async function testLabelingManyBuckets(t, saveInbetween) { - // We set MAXIMUM_BUCKET_COUNT to 150 and then label 199 = 75 (mag1) + 124 (downsampled) buckets in total. - // In between, we will save the data which allows the buckets of the first batch to be GC'ed. - // Therefore, saving the buckets of the second batch should not cause any problems. - t.context.model.getCubeByLayerName("segmentation").MAXIMUM_BUCKET_COUNT = 150; - - const oldCellId = 11; - const brushSize = 10; - const newCellId = 2; - - t.context.mocks.Request.sendJSONReceiveArraybufferWithHeaders = createBucketResponseFunction( - Uint16Array, - oldCellId, - 500, - ); - // Reload buckets which might have already been loaded before swapping the sendJSONReceiveArraybufferWithHeaders - // function. - await t.context.api.data.reloadAllBuckets(); - - // Prepare to paint into the center of 50 buckets. - const paintPositions1 = _.range(0, 50).map(idx => [32 * idx + 16, 32 * idx + 16, 32 * idx + 16]); - // Prepare to paint into the center of 50 other buckets. - const paintPositions2 = _.range(50, 100).map(idx => [ - 32 * idx + 16, - 32 * idx + 16, - 32 * idx + 16, - ]); - - Store.dispatch(updateUserSettingAction("brushSize", brushSize)); - Store.dispatch(setToolAction(AnnotationToolEnum.BRUSH)); - Store.dispatch(setActiveCellAction(newCellId)); - - for (const paintPosition of paintPositions1) { - Store.dispatch(setPositionAction(paintPosition)); - Store.dispatch(startEditingAction(paintPosition, OrthoViews.PLANE_XY)); - Store.dispatch(addToLayerAction(paintPosition)); - Store.dispatch(finishEditingAction()); - } - - if (saveInbetween) { - await t.context.api.tracing.save(); - } - - for (const paintPosition of paintPositions2) { - Store.dispatch(setPositionAction(paintPosition)); - Store.dispatch(startEditingAction(paintPosition, OrthoViews.PLANE_XY)); - Store.dispatch(addToLayerAction(paintPosition)); - Store.dispatch(finishEditingAction()); - } - - await t.context.api.tracing.save(); -} diff --git a/frontend/javascripts/test/snapshots/public/test-bundle/test/sagas/volumetracing_saga_integration.spec.js.md b/frontend/javascripts/test/snapshots/public/test-bundle/test/sagas/volumetracing/volumetracing_saga_integration.spec.js.md similarity index 99% rename from frontend/javascripts/test/snapshots/public/test-bundle/test/sagas/volumetracing_saga_integration.spec.js.md rename to frontend/javascripts/test/snapshots/public/test-bundle/test/sagas/volumetracing/volumetracing_saga_integration.spec.js.md index d53a5bdbc83..8814008d35c 100644 --- a/frontend/javascripts/test/snapshots/public/test-bundle/test/sagas/volumetracing_saga_integration.spec.js.md +++ b/frontend/javascripts/test/snapshots/public/test-bundle/test/sagas/volumetracing/volumetracing_saga_integration.spec.js.md @@ -1,4 +1,4 @@ -# Snapshot report for `public/test-bundle/test/sagas/volumetracing_saga_integration.spec.js` +# Snapshot report for `public/test-bundle/test/sagas/volumetracing/volumetracing_saga_integration.spec.js` The actual snapshot is saved in `volumetracing_saga_integration.spec.js.snap`. diff --git a/frontend/javascripts/test/snapshots/public/test-bundle/test/sagas/volumetracing_saga_integration.spec.js.snap b/frontend/javascripts/test/snapshots/public/test-bundle/test/sagas/volumetracing/volumetracing_saga_integration.spec.js.snap similarity index 100% rename from frontend/javascripts/test/snapshots/public/test-bundle/test/sagas/volumetracing_saga_integration.spec.js.snap rename to frontend/javascripts/test/snapshots/public/test-bundle/test/sagas/volumetracing/volumetracing_saga_integration.spec.js.snap