Skip to content

Commit

Permalink
Code coverage.
Browse files Browse the repository at this point in the history
  • Loading branch information
Kort Travis committed Nov 14, 2024
1 parent 90f526a commit f88a480
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 21 deletions.
3 changes: 1 addition & 2 deletions src/snapred/backend/error/ContinueWarning.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ def flags(self):
return self.model.flags

def __init__(self, message: str, flags: "Type" = 0):
ContinueWarning.Model.update_forward_refs()
ContinueWarning.Model.model_rebuild(force=True)
ContinueWarning.Model.model_rebuild(force=True) # replaces: `update_forward_refs` method
self.model = ContinueWarning.Model(message=message, flags=flags)
super().__init__(message)

Expand Down
3 changes: 1 addition & 2 deletions src/snapred/backend/error/RecoverableException.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ def data(self):
return self.model.data

def __init__(self, message: str, flags: "Type" = 0, data: Optional[Any] = None):
RecoverableException.Model.update_forward_refs()
RecoverableException.Model.model_rebuild(force=True)
RecoverableException.Model.model_rebuild(force=True) # replaces: `update_forward_refs` method
self.model = RecoverableException.Model(message=message, flags=flags, data=data)
logger.error(f"{extractTrueStacktrace()}")
super().__init__(message)
Expand Down
10 changes: 8 additions & 2 deletions src/snapred/backend/recipe/EffectiveInstrumentRecipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,23 @@ def queueAlgos(self):
Queues up the processing algorithms for the recipe.
Requires: unbagged groceries.
"""
# `EditInstrumentGeometry` modifies in-place, so we need to clone if a distinct output workspace is required.
if self.outputWS != self.inputWS:
self.mantidSnapper.CloneWorkspace(
"Clone workspace for reduced instrument",
OutputWorkspace=self.outputWS,
InputWorkspace=self.inputWS
)
self.mantidSnapper.EditInstrumentGeometry(
f"Editing instrument geometry for grouping '{self.unmaskedPixelGroup.focusGroup.name}'",
Workspace=self.inputWS,
Workspace=self.outputWS,
# TODO: Mantid defect: allow SI units here!
L2=np.rad2deg(self.unmaskedPixelGroup.L2),
Polar=np.rad2deg(self.unmaskedPixelGroup.twoTheta),
Azimuthal=np.rad2deg(self.unmaskedPixelGroup.azimuth),
#
InstrumentName=f"SNAP_{self.unmaskedPixelGroup.focusGroup.name}"
)
self.outputWS = self.inputWS

def validateInputs(self, ingredients: Ingredients, groceries: Dict[str, WorkspaceName]):
pass
Expand Down
13 changes: 4 additions & 9 deletions src/snapred/backend/service/ReductionService.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,6 @@ def reduction(self, request: ReductionRequest):
groceries = self.fetchReductionGroceries(request)

ingredients = self.prepReductionIngredients(request, groceries.get("combinedPixelMask"))
ingredients.artificialNormalizationIngredients = request.artificialNormalizationIngredients

# attach the list of grouping workspaces to the grocery dictionary
groceries["groupingWorkspaces"] = groupingResults["groupingWorkspaces"]
Expand Down Expand Up @@ -314,13 +313,7 @@ def prepReductionIngredients(self, request: ReductionRequest, combinedPixelMask:
Prepare the needed ingredients for calculating reduction.
Requires:
- runNumber
- lite mode flag
- timestamp
- at least one focus group specified
- a smoothing parameter
- a calibrant sample path
- a peak threshold
- reduction request
- an optional combined mask workspace
:param request: a reduction request
Expand All @@ -338,7 +331,9 @@ def prepReductionIngredients(self, request: ReductionRequest, combinedPixelMask:
versions=request.versions,
)
# TODO: Skip calibrant sample if there is no calibrant
return self.sousChef.prepReductionIngredients(farmFresh, combinedPixelMask)
ingredients = self.sousChef.prepReductionIngredients(farmFresh, combinedPixelMask)
ingredients.artificialNormalizationIngredients = request.artificialNormalizationIngredients
return ingredients

@FromString
def fetchReductionGroceries(self, request: ReductionRequest) -> Dict[str, Any]:
Expand Down
8 changes: 8 additions & 0 deletions tests/unit/backend/data/test_LocalDataService.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,14 @@ def test_stateExists():
assert localDataService.stateExists("12345")


def test_stateExists_not():
# Test that the 'stateExists' method returns False when the state doesn't exist.
localDataService = LocalDataService()
localDataService.constructCalibrationStateRoot = mock.Mock(return_value=Path("a/non-existent/path"))
localDataService.generateStateId = mock.Mock(return_value=(ENDURING_STATE_ID, None))
assert not localDataService.stateExists("12345")


@mock.patch(ThisService + "GetIPTS")
def test_calibrationFileExists(GetIPTS): # noqa ARG002
localDataService = LocalDataService()
Expand Down
50 changes: 48 additions & 2 deletions tests/unit/backend/recipe/test_EffectiveInstrumentRecipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,64 @@ def test_unbagGroceries_output_default(self):
assert recipe.outputWS == groceries["inputWorkspace"]

def test_queueAlgos(self):
recipe = EffectiveInstrumentRecipe()
ingredients = self.ingredients
groceries = {"inputWorkspace": mock.Mock(), "outputWorkspace": mock.Mock()}
recipe.prep(ingredients, groceries)
recipe.queueAlgos()

queuedAlgos = recipe.mantidSnapper._algorithmQueue

cloneWorkspaceTuple = queuedAlgos[0]
assert cloneWorkspaceTuple[0] == "CloneWorkspace"
assert cloneWorkspaceTuple[2]["InputWorkspace"] == groceries["inputWorkspace"]
assert cloneWorkspaceTuple[2]["OutputWorkspace"] == groceries["outputWorkspace"]

editInstrumentGeometryTuple = queuedAlgos[1]
assert editInstrumentGeometryTuple[0] == "EditInstrumentGeometry"
assert editInstrumentGeometryTuple[2]["Workspace"] == groceries["outputWorkspace"]

def test_queueAlgos_default(self):
recipe = EffectiveInstrumentRecipe()
ingredients = self.ingredients
groceries = {"inputWorkspace": mock.Mock()}
recipe.prep(ingredients, groceries)
recipe.queueAlgos()

queuedAlgos = recipe.mantidSnapper._algorithmQueue
editInstrumentGeometryTuple = queuedAlgos[0]


editInstrumentGeometryTuple = queuedAlgos[0]
assert editInstrumentGeometryTuple[0] == "EditInstrumentGeometry"
assert editInstrumentGeometryTuple[2]["Workspace"] == groceries["inputWorkspace"]

def test_cook(self):
utensils = Utensils()
mockSnapper = mock.Mock()
utensils.mantidSnapper = mockSnapper
recipe = EffectiveInstrumentRecipe(utensils=utensils)
ingredients = self.ingredients
groceries = {"inputWorkspace": mock.Mock(), "outputWorkspace": mock.Mock()}

output = recipe.cook(ingredients, groceries)

assert output == groceries["outputWorkspace"]

assert mockSnapper.executeQueue.called
mockSnapper.CloneWorkspace.assert_called_once_with(
"Clone workspace for reduced instrument",
OutputWorkspace=groceries["outputWorkspace"],
InputWorkspace=groceries["inputWorkspace"]
)
mockSnapper.EditInstrumentGeometry.assert_called_once_with(
f"Editing instrument geometry for grouping '{ingredients.unmaskedPixelGroup.focusGroup.name}'",
Workspace=groceries["outputWorkspace"],
L2=np.rad2deg(ingredients.unmaskedPixelGroup.L2),
Polar=np.rad2deg(ingredients.unmaskedPixelGroup.twoTheta),
Azimuthal=np.rad2deg(ingredients.unmaskedPixelGroup.azimuth),
InstrumentName=f"SNAP_{ingredients.unmaskedPixelGroup.focusGroup.name}"
)

def test_cook_default(self):
utensils = Utensils()
mockSnapper = mock.Mock()
utensils.mantidSnapper = mockSnapper
Expand All @@ -97,6 +142,7 @@ def test_cook(self):
assert output == groceries["inputWorkspace"]

assert mockSnapper.executeQueue.called
mockSnapper.CloneWorkspace.assert_not_called()
mockSnapper.EditInstrumentGeometry.assert_called_once_with(
f"Editing instrument geometry for grouping '{ingredients.unmaskedPixelGroup.focusGroup.name}'",
Workspace=groceries["inputWorkspace"],
Expand Down
83 changes: 79 additions & 4 deletions tests/unit/backend/service/test_ReductionService.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
)
from snapred.backend.api.RequestScheduler import RequestScheduler
from snapred.backend.dao import WorkspaceMetadata
from snapred.backend.dao.ingredients import ArtificialNormalizationIngredients
from snapred.backend.dao.ingredients.ReductionIngredients import ReductionIngredients
from snapred.backend.dao.reduction.ReductionRecord import ReductionRecord
from snapred.backend.dao.request import (
CreateArtificialNormalizationRequest,
FarmFreshIngredients,
ReductionExportRequest,
ReductionRequest,
)
Expand Down Expand Up @@ -74,7 +76,10 @@ def setUp(self):
timestamp=self.instance.getUniqueTimestamp(),
versions=(1, 2),
pixelMasks=[],
keepUnfocused=True,
convertUnitsTo="TOF",
focusGroups=[FocusGroup(name="apple", definition="path/to/grouping")],
artificialNormalizationIngredients=mock.Mock(spec=ArtificialNormalizationIngredients)
)

def test_name(self):
Expand All @@ -101,10 +106,22 @@ def test_fetchReductionGroupings(self):

def test_prepReductionIngredients(self):
# Call the method with the provided parameters
res = self.instance.prepReductionIngredients(self.request)

assert ReductionIngredients.model_validate(res)
assert res == self.instance.sousChef.prepReductionIngredients(self.request)
result = self.instance.prepReductionIngredients(self.request)

farmFresh = FarmFreshIngredients(
runNumber=self.request.runNumber,
useLiteMode=self.request.useLiteMode,
timestamp=self.request.timestamp,
focusGroups=self.request.focusGroups,
keepUnfocused=self.request.keepUnfocused,
convertUnitsTo=self.request.convertUnitsTo,
versions=self.request.versions,
)
expected = self.instance.sousChef.prepReductionIngredients(farmFresh)
expected.artificialNormalizationIngredients = self.request.artificialNormalizationIngredients

assert ReductionIngredients.model_validate(result)
assert result == expected

def test_fetchReductionGroceries(self):
self.instance.dataFactoryService.getThisOrLatestCalibrationVersion = mock.Mock(return_value=1)
Expand Down Expand Up @@ -140,6 +157,64 @@ def test_reduction(self, mockReductionRecipe):
mockReductionRecipe.return_value.cook.assert_called_once_with(ingredients, groceries)
assert result.record.workspaceNames == mockReductionRecipe.return_value.cook.return_value["outputs"]

@mock.patch(thisService + "ReductionResponse")
@mock.patch(thisService + "ReductionRecipe")
def test_reduction_full_sequence(self, mockReductionRecipe, mockReductionResponse):
mockReductionRecipe.return_value = mock.Mock()
mockResult = {
"result": True,
"outputs": ["one", "two", "three"],
"unfocusedWS": mock.Mock()
}
mockReductionRecipe.return_value.cook = mock.Mock(return_value=mockResult)
self.instance.dataFactoryService.getThisOrLatestCalibrationVersion = mock.Mock(return_value=1)
self.instance.dataFactoryService.stateExists = mock.Mock(return_value=True)
self.instance.dataFactoryService.calibrationExists = mock.Mock(return_value=True)
self.instance.dataFactoryService.getThisOrLatestNormalizationVersion = mock.Mock(return_value=1)
self.instance.dataFactoryService.normalizationExists = mock.Mock(return_value=True)
self.instance._markWorkspaceMetadata = mock.Mock()

self.instance.fetchReductionGroupings = mock.Mock(
return_value={
"focusGroups": mock.Mock(),
"groupingWorkspaces": mock.Mock()
}
)
self.instance.fetchReductionGroceries = mock.Mock(
return_value={
"combinedPixelMask": mock.Mock()
}
)
self.instance.prepReductionIngredients = mock.Mock(
return_value=mock.Mock()
)
self.instance._createReductionRecord = mock.Mock(
return_value=mock.Mock()
)

request_ = self.request.model_copy()
result = self.instance.reduction(request_)

self.instance.fetchReductionGroupings.assert_called_once_with(request_)
assert request_.focusGroups == self.instance.fetchReductionGroupings.return_value["focusGroups"]
self.instance.fetchReductionGroceries.assert_called_once_with(request_)
self.instance.prepReductionIngredients.assert_called_once_with(
request_,
self.instance.fetchReductionGroceries.return_value["combinedPixelMask"]
)
assert self.instance.fetchReductionGroceries.return_value["groupingWorkspaces"] ==\
self.instance.fetchReductionGroupings.return_value["groupingWorkspaces"]

self.instance._createReductionRecord.assert_called_once_with(
request_,
self.instance.prepReductionIngredients.return_value,
mockReductionRecipe.return_value.cook.return_value["outputs"]
)
mockReductionResponse.assert_called_once_with(
record=self.instance._createReductionRecord.return_value,
unfocusedData=mockReductionRecipe.return_value.cook.return_value["unfocusedWS"]
)

def test_reduction_noState_withWritePerms(self):
mockRequest = mock.Mock()
self.instance.dataFactoryService.stateExists = mock.Mock(return_value=False)
Expand Down

0 comments on commit f88a480

Please sign in to comment.