Test_Scenarios.pf Source File


This file depends on

sourcefile~~test_scenarios.pf~~EfferentGraph sourcefile~test_scenarios.pf Test_Scenarios.pf sourcefile~esmf_testcase.f90 ESMF_TestCase.F90 sourcefile~test_scenarios.pf->sourcefile~esmf_testcase.f90 sourcefile~esmf_testparameter.f90 ESMF_TestParameter.F90 sourcefile~test_scenarios.pf->sourcefile~esmf_testparameter.f90 sourcefile~esmf_utilities.f90 ESMF_Utilities.F90 sourcefile~test_scenarios.pf->sourcefile~esmf_utilities.f90 sourcefile~genericgridcomp.f90 GenericGridComp.F90 sourcefile~test_scenarios.pf->sourcefile~genericgridcomp.f90 sourcefile~griddedcomponentdriver.f90 GriddedComponentDriver.F90 sourcefile~test_scenarios.pf->sourcefile~griddedcomponentdriver.f90 sourcefile~mapl_generic.f90 MAPL_Generic.F90 sourcefile~test_scenarios.pf->sourcefile~mapl_generic.f90 sourcefile~multistate.f90 MultiState.F90 sourcefile~test_scenarios.pf->sourcefile~multistate.f90 sourcefile~outermetacomponent.f90 OuterMetaComponent.F90 sourcefile~test_scenarios.pf->sourcefile~outermetacomponent.f90 sourcefile~usersetservices.f90 UserSetServices.F90 sourcefile~test_scenarios.pf->sourcefile~usersetservices.f90

Source Code

#include "MAPL_TestErr.h"

module Test_Scenarios

   use mapl3g_Generic
   use mapl3g_GenericPhases
   use mapl3g_MultiState
   use mapl3g_OuterMetaComponent
   use mapl3g_GriddedComponentDriver
   use mapl3g_GenericGridComp, generic_setservices => setservices
   use mapl3g_UserSetServices
   use mapl3g_ESMF_Utilities
   use esmf
   use nuopc
   ! testing framework
   use ESMF_TestCase_mod
   use ESMF_TestParameter_mod
   use funit

   implicit none

   abstract interface
      subroutine I_check_stateitem(expectations, state, short_name, description, rc)
         import ESMF_HConfig, ESMF_State
         type(ESMF_HConfig), intent(in) :: expectations
         type(ESMF_State), intent(inout) :: state
         character(*), intent(in) :: short_name
         character(*), intent(in) :: description
         integer, intent(out) :: rc
      end subroutine I_check_stateitem
   end interface

   @testParameter
   type, extends(ESMF_TestParameter) :: ScenarioDescription
      character(:), allocatable :: name
      character(:), allocatable :: root
      character(:), allocatable :: check_name
      procedure(I_check_stateitem), nopass, pointer :: check_stateitem
   contains
      procedure :: tostring => tostring_description
   end type ScenarioDescription

   @testCase(constructor=Scenario, testParameters={get_parameters()})
   type, extends(ESMF_TestCase) :: Scenario
      character(:), allocatable :: scenario_name
      character(:), allocatable :: scenario_root
      character(:), allocatable :: check_name
      procedure(I_check_stateitem), nopass, pointer :: check_stateitem

      type(ESMF_HConfig), allocatable :: expectations
      type(ESMF_GridComp) :: outer_gc
      type(MultiState) :: outer_states
      type(ESMF_Grid) :: grid
   contains
      procedure :: setup
      procedure :: tearDown
   end type Scenario

   interface Scenario
      procedure :: new_Scenario
   end interface Scenario

   interface ScenarioDescription
      procedure :: new_ScenarioDescription
   end interface ScenarioDescription

contains

   function new_Scenario(desc) result(s)
      type(ScenarioDescription), intent(in) :: desc
      type(Scenario) :: s
      s%scenario_name = desc%name
      s%scenario_root = desc%root
      s%check_name = desc%check_name
      s%check_stateitem => desc%check_stateitem
   end function new_Scenario

   function new_ScenarioDescription(name, root, check_name, check_stateitem) result(s)
      type(ScenarioDescription) :: s
      character(*), intent(in) :: name
      character(*), intent(in) :: root
      character(*), intent(in) :: check_name
      procedure(I_check_stateitem) :: check_stateitem
      s%name = name
      s%root = root
      s%check_name = check_name
      s%check_stateitem => check_stateitem

      call s%setNumPETsRequested(1)
   end function new_ScenarioDescription

   function get_parameters() result(params)

      type(ScenarioDescription), allocatable :: params(:)

      params = [ScenarioDescription:: ]

      ! Field oriented tests
      params = [params, add_params('item exist', check_item_type)]
      params = [params, add_params('field status', check_field_status)]
      params = [params, add_params('field typekind', check_field_typekind)]
      params = [params, add_params('field value', check_field_value)]
      params = [params, add_params('field k_values', check_field_k_values)]
      params = [params, add_params('field exists', check_field_rank)]

      ! Service oriented tests
      params = [params, ScenarioDescription('service_service', 'parent.yaml', 'field count', check_fieldcount)]

   contains

      function add_params(check_name, check_stateitem) result(params)
         type(ScenarioDescription), allocatable :: params(:)
         character(*), intent(in) :: check_name
         procedure(I_check_stateitem) :: check_stateitem

         params = [ &
              ScenarioDescription('scenario_1',              'parent.yaml',      check_name, check_stateitem), &
              ScenarioDescription('scenario_2',              'parent.yaml',      check_name, check_stateitem), &
              ScenarioDescription('scenario_reexport_twice', 'grandparent.yaml', check_name, check_stateitem), &
              ScenarioDescription('history_1',               'cap.yaml',         check_name, check_stateitem), &
              ScenarioDescription('history_wildcard',        'cap.yaml',         check_name, check_stateitem), &
              ScenarioDescription('extdata_1',               'cap.yaml',         check_name, check_stateitem), &
              ScenarioDescription('precision_extension',     'parent.yaml',      check_name, check_stateitem), &
              ScenarioDescription('precision_extension_3d',  'parent.yaml',      check_name, check_stateitem), &
              ScenarioDescription('3d_specs',                'parent.yaml',      check_name, check_stateitem), &
              ScenarioDescription('ungridded_dims',          'parent.yaml',      check_name, check_stateitem), &
              ScenarioDescription('service_service',         'parent.yaml',      check_name, check_stateitem), &
              ScenarioDescription('export_dependency',       'parent.yaml',      check_name, check_stateitem), &
              ScenarioDescription('regrid',                  'cap.yaml',         check_name, check_stateitem), &
              ScenarioDescription('propagate_geom',          'parent.yaml',      check_name, check_stateitem), &
              ScenarioDescription('vertical_regridding',     'parent.yaml',      check_name, check_stateitem)  &
#ifndef __GFORTRAN__
              , &
              ScenarioDescription('vertical_regridding_2',   'parent.yaml',      check_name, check_stateitem), &
              ScenarioDescription('vertical_regridding_3',   'AGCM.yaml',        check_name, check_stateitem)  &
#endif
              ]
      end function add_params

   end function get_parameters

   subroutine setup(this)
      class(Scenario), intent(inout) :: this

      type(ESMF_HConfig) :: config
      integer :: status, user_status
      integer :: i
      type(ESMF_State) :: importState, exportState
      character(:), allocatable :: file_name
      type(ESMF_Time) :: t
      type(ESMF_TimeInterval) :: dt
      type(ESMF_Clock) :: clock

      file_name = './scenarios/' // this%scenario_name // '/' // this%scenario_root
      config = ESMF_HConfigCreate(filename=file_name)

      call NUOPC_FieldDictionarySetup(filename='./scenarios/FieldDictionary.yml', _RC)
      @assert_that(status, is(0))

      associate (outer_gc => this%outer_gc, outer_states => this%outer_states, grid => this%grid)

        call ESMF_TimeSet(t, h=0)
        call ESMF_TimeIntervalSet(dt, h=1)
        clock = ESMF_ClockCreate(dt, t)

        outer_gc = create_grid_comp('ROOT', user_setservices('libsimple_parent_gridcomp'), config, clock, _RC)
        call ESMF_GridCompSetServices(outer_gc, generic_setServices, userRC=user_status, _RC)
        _VERIFY(user_status)

        importState = ESMF_StateCreate(_RC)
        exportState = ESMF_StateCreate(_RC)
        outer_states = MultiState(importState=importState, exportState=exportState)

        do i = 1, size(GENERIC_INIT_PHASE_SEQUENCE)
           associate (phase => GENERIC_INIT_PHASE_SEQUENCE(i))
             call ESMF_GridCompInitialize(outer_gc, &
                  importState=importState, exportState=exportState, clock=clock, &
                  phase=phase, userRC=user_status, _RC)
             _VERIFY(user_status)
           end associate
        end do

        call ESMF_GridCompRun(outer_gc, &
             importState=importState, exportState=exportState, clock=clock, &
             userRC=user_status, phase=GENERIC_RUN_USER, _RC)
        _VERIFY(user_status)

      end associate

      file_name = './scenarios/' // this%scenario_name // '/expectations.yaml'
      this%expectations = ESMF_HConfigCreate(filename=file_name, _RC)
   end subroutine setup

   ! In theory we want to call finalize here and then destroy ESMF objects in this
   subroutine teardown(this)
      class(Scenario), intent(inout) :: this

      integer :: status

      !# call ESMF_GridCompDestroy(this%outer_gc, _RC)

      !# call ESMF_StateDestroy(this%outer_states%importState,_RC)
      !# call ESMF_StateDestroy(this%outer_states%exportState, _RC)
   end subroutine teardown

   @test
   subroutine test_anything(this)

      class(Scenario), intent(inout) :: this

      integer :: status
      integer :: i
      character(:), allocatable :: comp_path, item_name
      type(ESMF_HConfig) :: comp_expectations, expected_properties
      type(MultiState) :: comp_states
      type(ESMF_HConfig) :: state_items
      integer :: item_count, expected_item_count
      type(ESMF_Field) :: field
      type(ESMF_FieldStatus_Flag) :: expected_field_status, field_status
      character(:), allocatable :: expected_status

      components: do i = 1, ESMF_HConfigGetSize(this%expectations)
         comp_expectations = ESMF_HConfigCreateAt(this%expectations,index=i,_RC)
         comp_path = ESMF_HConfigAsString(comp_expectations,keyString='component',_RC)
         call get_substates(this%outer_gc, this%outer_states, comp_path, substates=comp_states, _RC)

         call check_items_in_state('import', _RC)
         call check_items_in_state('export', _RC)
         call check_items_in_state('internal', _RC)
      end do components

   contains

      subroutine check_items_in_state(state_intent, rc)
         character(*), intent(in) :: state_intent
         integer, intent(out) :: rc

         integer :: status
         type(ESMF_HConfig) :: state_items
         type(ESMF_State) :: state
         character(:), allocatable :: msg
         type(ESMF_HConfigIter) :: hconfigIter,hconfigIterBegin,hconfigIterEnd

         rc = -1

         if (.not. ESMF_HConfigIsDefined(comp_expectations,keyString=state_intent)) then
            rc = 0 ! that's ok
            return
         end if

         call comp_states%get_state(state, state_intent, _RC)

         msg = comp_path // '::' // state_intent

         state_items = ESMF_HConfigCreateAt(comp_expectations,keyString=state_intent,_RC)
         @assertTrue(ESMF_HConfigIsMap(state_items), msg)

         hconfigIter = ESMF_HConfigIterBegin(state_items)
         hconfigIterBegin = ESMF_HConfigIterBegin(state_items)
         hconfigIterEnd = ESMF_HConfigIterEnd(state_items)
         do while (ESMF_HConfigIterLoop(hconfigIter,hconfigIterBegin,hconfigIterEnd))
            item_name = ESMF_HConfigAsStringMapKey(hconfigIter,_RC)
            expected_properties = ESMF_HConfigCreateAtMapVal(hconfigIter,_RC)

            msg = comp_path // '::' // state_intent // '::' // item_name

            associate (test_description => msg // '::' // this%check_name)
              call this%check_stateitem(expected_properties, state, item_name, test_description, _RC)
            end associate
         end do

         rc = 0
      end subroutine check_items_in_state

   end subroutine test_anything

   function get_itemtype(state, short_name, rc) result(itemtype)
      type(ESMF_StateItem_Flag) :: itemtype
      type(ESMF_State) :: state
      character(*), intent(in) :: short_name
      integer, intent(out) :: rc

      integer :: status
      integer :: idx
      type(ESMF_State) :: substate, tmp_state
      character(:), allocatable :: name

      integer :: itemcount

      rc = 0
      name = short_name
      substate = state

      do
         idx = index(name, '/')
         if (idx == 0) then
            call ESMF_StateGet(substate, name, itemtype=itemtype, _RC)
            return
         end if
         call ESMF_StateGet(substate, name(:idx-1), tmp_state, rc=status)
         @assert_that(short_name, status, is(0))
         name = name(idx+1:)
         substate = tmp_state
      end do

      rc = 0
   end function get_itemtype

   subroutine check_item_type(expectations, state, short_name, description, rc)

      type(ESMF_HConfig), intent(in) :: expectations
      type(ESMF_State), intent(inout) :: state
      character(*), intent(in) :: short_name
      character(*), intent(in) :: description
      integer, intent(out) :: rc

      type(ESMF_StateItem_Flag) :: expected_itemtype, itemtype
      character(len=:), allocatable :: msg
      integer :: status
      integer :: idx

      msg = description
      expected_itemtype = get_expected_itemtype(expectations, _RC)

      itemtype = get_itemtype(state, short_name, _RC)
      @assert_that(msg // ':: check item type of '//short_name, expected_itemtype == itemtype, is(true()))

      rc = 0

   contains

      function get_expected_itemtype(expectations, rc) result(expected_itemtype)
         type(ESMF_StateItem_Flag) :: expected_itemtype
         type(ESMF_HConfig), intent(in) :: expectations
         integer, intent(out) :: rc

         character(:), allocatable :: itemtype_str
         integer :: status

         if (.not. ESMF_HConfigIsDefined(expectations,keyString='class')) then
            expected_itemtype = ESMF_STATEITEM_FIELD
            rc=0
            return
         end if

         itemtype_str= ESMF_HConfigAsString(expectations,keyString='class',_RC)

         select case (itemtype_str)
         case ('field')
            expected_itemtype = ESMF_STATEITEM_FIELD
         case ('bundle')
            expected_itemtype = ESMF_STATEITEM_FIELDBUNDLE
         case default
            expected_itemtype = ESMF_STATEITEM_UNKNOWN
         end select

         rc = 0
      end function get_expected_itemtype

   end subroutine check_item_type

   subroutine check_field_status(expectations, state, short_name, description, rc)
      type(ESMF_HConfig), intent(in) :: expectations
      type(ESMF_State), intent(inout) :: state
      character(*), intent(in) :: short_name
      character(*), intent(in) :: description
      integer, intent(out) :: rc

      character(len=:), allocatable :: expected_field_status_str
      type(ESMF_FieldStatus_Flag) :: expected_field_status
      type(ESMF_FieldStatus_Flag) :: found_field_status
      type(ESMF_StateItem_Flag) :: itemtype
      type(ESMF_Field) :: field
      integer :: status
      character(len=:), allocatable :: msg

      msg = short_name // ':: '// description

      call ESMF_StateGet(state, short_name, itemtype=itemtype, _RC)
      if (itemtype /= ESMF_STATEITEM_FIELD) then
         rc = 0
         return
      end if

      expected_field_status_str = ESMF_HConfigAsString(expectations,keyString='status',_RC)
      expected_field_status = ESMF_FIELDSTATUS_GRIDSET
      select case (expected_field_status_str)
      case ('complete')
         expected_field_status = ESMF_FIELDSTATUS_COMPLETE
      case ('gridset')
         expected_field_status = ESMF_FIELDSTATUS_GRIDSET
      case ('empty')
         expected_field_status = ESMF_FIELDSTATUS_EMPTY
      case default
         _VERIFY(-1)
      end select

      call ESMF_StateGet(state, short_name, field, _RC)
      call ESMF_FieldGet(field, status=found_field_status, _RC)
      @assert_that(msg // ' field status: ',expected_field_status == found_field_status, is(true()))

      rc = 0
   end subroutine check_field_status

   subroutine check_field_typekind(expectations, state, short_name, description, rc)
      type(ESMF_HConfig), intent(in) :: expectations
      type(ESMF_State), intent(inout) :: state
      character(*), intent(in) :: short_name
      character(*), intent(in) :: description
      integer, intent(out) :: rc

      character(len=:), allocatable :: expected_field_typekind_str
      type(ESMF_TypeKind_Flag) :: expected_field_typekind
      type(ESMF_TypeKind_Flag) :: found_field_typekind
      type(ESMF_StateItem_Flag) :: itemtype
      integer :: status
      character(len=:), allocatable :: msg
      type(ESMF_Field) :: field

      msg = description

      call ESMF_StateGet(state, short_name, itemtype=itemtype, _RC)
      if (itemtype /= ESMF_STATEITEM_FIELD) then
         rc = 0
         return
      end if

      if (.not. ESMF_HConfigIsDefined(expectations,keyString='typekind')) then
         rc = 0
         return
      end if

      expected_field_typekind_str = ESMF_HConfigAsString(expectations,keyString='typekind',_RC)
      select case (expected_field_typekind_str)
      case ('R4')
         expected_field_typekind = ESMF_TYPEKIND_R4
      case ('R8')
         expected_field_typekind = ESMF_TYPEKIND_R8
      case default
         _VERIFY(-1)
      end select

      call ESMF_StateGet(state, short_name, field, _RC)
      call ESMF_FieldGet(field, typekind=found_field_typekind, _RC)
      @assert_that(msg // ' field typekind: ',expected_field_typekind == found_field_typekind, is(true()))

      rc = 0
   end subroutine check_field_typekind

   subroutine check_field_value(expectations, state, short_name, description, rc)
      type(ESMF_HConfig), intent(in) :: expectations
      type(ESMF_State), intent(inout) :: state
      character(*), intent(in) :: short_name
      character(*), intent(in) :: description
      integer, intent(out) :: rc

      real :: expected_field_value
      integer :: rank
      type(ESMF_TypeKind_Flag) :: typekind
      integer :: status
      character(len=:), allocatable :: msg
      type(ESMF_Field) :: field
      type(ESMF_StateItem_Flag) :: itemtype

      msg = description

      itemtype = get_itemtype(state, short_name, _RC)
      if (itemtype /= ESMF_STATEITEM_FIELD) then ! that's ok
         rc = 0
         return
      end if

      if (.not. ESMF_HConfigIsDefined(expectations,keyString='value')) then
         rc = 0
         return
      end if

      expected_field_value = ESMF_HConfigAsR4(expectations,keyString='value',_RC)

      call ESMF_StateGet(state, short_name, field, _RC)
      call ESMF_FieldGet(field, typekind=typekind, rank=rank, rc=status)
      @assert_that('field get failed '//short_name,  status, is(0))

      if (typekind == ESMF_TYPEKIND_R4) then
         block
            real(kind=ESMF_KIND_R4), pointer :: x2(:,:),x3(:,:,:),x4(:,:,:,:)
            select case(rank)
            case(2)
               call ESMF_FieldGet(field, farrayPtr=x2, _RC)
               @assert_that('value of '//short_name, all(x2 == expected_field_value), is(true()))
            case(3)
               call ESMF_FieldGet(field, farrayPtr=x3, _RC)
               @assert_that('value of '//short_name, all(x3 == expected_field_value), is(true()))
            case(4)
               call ESMF_FieldGet(field, farrayPtr=x4, _RC)
               @assert_that('value of '//short_name, all(x4 == expected_field_value), is(true()))
            end select
         end block
      elseif (typekind == ESMF_TYPEKIND_R8) then
         block
            real(kind=ESMF_KIND_R8), pointer :: x2(:,:),x3(:,:,:),x4(:,:,:,:)
            select case(rank)
            case(2)
               call ESMF_FieldGet(field, farrayPtr=x2, _RC)
               @assert_that('value of '//short_name, all(x2 == expected_field_value), is(true()))
            case(3)
               call ESMF_FieldGet(field, farrayPtr=x3, _RC)
               @assert_that('value of '//short_name, all(x3 == expected_field_value), is(true()))
            case(4)
               call ESMF_FieldGet(field, farrayPtr=x4, _RC)
               @assert_that('value of '//short_name, all(x4 == expected_field_value), is(true()))
            end select
         end block
      else
         _VERIFY(-1)
      end if

      rc = 0
   end subroutine check_field_value

   subroutine check_field_k_values(expectations, state, short_name, description, rc)
      type(ESMF_HConfig), intent(in) :: expectations
      type(ESMF_State), intent(inout) :: state
      character(*), intent(in) :: short_name
      character(*), intent(in) :: description
      integer, intent(out) :: rc

      real, allocatable :: expected_k_values(:)
      integer :: rank
      type(ESMF_TypeKind_Flag) :: typekind
      integer :: status
      character(len=:), allocatable :: msg
      type(ESMF_Field) :: field
      type(ESMF_StateItem_Flag) :: itemtype
      integer :: i, j, l, shape3(3), shape4(4)

      msg = description

      itemtype = get_itemtype(state, short_name, _RC)
      if (itemtype /= ESMF_STATEITEM_FIELD) then ! that's ok
         rc = 0
         return
      end if

      if (.not. ESMF_HConfigIsDefined(expectations,keyString='k_values')) then
         rc = 0
         return
      end if

      expected_k_values = ESMF_HConfigAsR4Seq(expectations,keyString='k_values',_RC)

      call ESMF_StateGet(state, short_name, field, _RC)
      call ESMF_FieldGet(field, typekind=typekind, rank=rank, rc=status)
      @assert_that('field get failed '//short_name,  status, is(0))

      if (typekind == ESMF_TYPEKIND_R4) then
         block
            real(kind=ESMF_KIND_R4), pointer :: x3(:, :, :), x4(:, :, :, :)
            select case(rank)
            case(3)
               call ESMF_FieldGet(field, farrayPtr=x3, _RC)
               shape3 = shape(x3)
               do i = 1, shape3(1)
                  do j = 1, shape3(2)
                     @assert_that("value of "//short_name, x3(i, j, :), is(equal_to(expected_k_values)))
                  end do
               end do
            case(4)
               call ESMF_FieldGet(field, farrayPtr=x4, _RC)
               shape4 = shape(x4)
               do i = 1, shape4(1)
                  do j = 1, shape4(2)
                     do l = 1, shape4(4)
                        @assert_that("value of "//short_name, x4(i, j, :, l), is(equal_to(expected_k_values)))
                     end do
                  end do
               end do
            case default
               error stop "invalid rank"
            end select
         end block
      elseif (typekind == ESMF_TYPEKIND_R8) then
         block
            real(kind=ESMF_KIND_R8), pointer :: x3(:, :, :), x4(:, :, :, :)
            select case(rank)
            case(3)
               call ESMF_FieldGet(field, farrayPtr=x3, _RC)
               shape3 = shape(x3)
               do i = 1, shape3(1)
                  do j = 1, shape3(2)
                     @assert_that("value of "//short_name, x3(i, j, :), is(equal_to(expected_k_values)))
                  end do
               end do
            case(4)
               call ESMF_FieldGet(field, farrayPtr=x4, _RC)
               shape4 = shape(x4)
               do i = 1, shape4(1)
                  do j = 1, shape4(2)
                     do l = 1, shape4(4)
                        @assert_that("value of "//short_name, x4(i, j, :, l), is(equal_to(expected_k_values)))
                     end do
                  end do
               end do
            case default
               error stop "invalid rank"
            end select
         end block
      else
         _VERIFY(-1)
      end if

      rc = 0
   end subroutine check_field_k_values

   subroutine check_field_rank(expectations, state, short_name, description, rc)
      type(ESMF_HConfig), intent(in) :: expectations
      type(ESMF_State), intent(inout) :: state
      character(*), intent(in) :: short_name
      character(*), intent(in) :: description
      integer, intent(out) :: rc

      type(ESMF_Field) :: field
      integer :: expected_field_rank
      integer :: rank
      integer :: status
      type(ESMF_StateItem_Flag) :: itemtype
      character(len=:), allocatable :: msg

      msg = description

      if (.not. ESMF_HConfigIsDefined(expectations,keyString='rank')) then
         rc = 0
         return
      end if

      call ESMF_StateGet(state, short_name, itemtype=itemtype)
      if (itemtype /= ESMF_STATEITEM_FIELD) then ! that's ok
         rc = 0
         return
      end if

      call ESMF_StateGet(state, short_name, field, _RC)
      call ESMF_FieldGet(field, rank=rank, _RC)

      expected_field_rank = ESMF_HConfigAsI4(expectations,keyString='rank',_RC)
      @assert_that(msg // 'field rank:', rank == expected_field_rank, is(true()))

      rc = 0
   end subroutine check_field_rank

   subroutine check_fieldCount(expectations, state, short_name, description, rc)
      type(ESMF_HConfig), intent(in) :: expectations
      type(ESMF_State), intent(inout) :: state
      character(*), intent(in) :: short_name
      character(*), intent(in) :: description
      integer, intent(out) :: rc

      integer :: status
      character(len=:), allocatable :: msg
      integer :: found_fieldCount, expected_fieldCount
      type(ESMF_FieldBundle) :: bundle
      type(ESMF_StateItem_Flag) :: itemtype

      msg = description

      rc = 0

      call ESMF_StateGet(state, short_name, itemtype=itemtype)
      if (itemtype /= ESMF_STATEITEM_FIELDBUNDLE) return ! that's ok

      if (.not. ESMF_HConfigIsDefined(expectations,keyString='fieldcount')) return

      expected_fieldCount = ESMF_HConfigAsI4(expectations,keyString='fieldcount',_RC)
      call ESMF_StateGet(state, short_name, bundle, _RC)
      call ESMF_FieldBundleGet(bundle, fieldCount=found_fieldCount, _RC)

      @assert_that(found_fieldCount, is(expected_fieldCount))
   end subroutine check_fieldCount

   recursive subroutine get_substates(gc, states, component_path, substates, rc)
      use mapl3g_GriddedComponentDriver
      type(ESMF_GridComp), target, intent(inout) :: gc
      type(MultiState), intent(in) :: states
      character(*), intent(in) :: component_path
      type(MultiState), intent(out) :: substates
      integer, intent(out) :: rc

      integer :: status
      character(:), allocatable :: child_name
      type(GriddedComponentDriver) :: child
      type(ESMF_GridComp) :: child_gc
      type(OuterMetaComponent), pointer :: outer_meta
      integer :: idx
      type(GriddedComponentDriver), pointer :: user_component

      rc = 0

      if (component_path == '<root>' .or. component_path == '') then
         substates = states
         return
      end if

      outer_meta => get_outer_meta(gc, _RC)

      ! Parse path
      idx = index(component_path, '/')
      if (idx == 0) idx = len(component_path) + 1
      child_name = component_path(:idx-1)

      if (child_name == '<user>') then
         user_component => outer_meta%get_user_gc_driver()
         substates = user_component%get_states()
         return
      end if

      ! Otherwise drill down 1 level.
      child = outer_meta%get_child(child_name, _RC)

      child_gc = child%get_gridcomp()

      call get_substates(child_gc, child%get_states(), component_path(idx+1:), substates, _RC)

      return
   end subroutine get_substates

   function tostring_description(this) result(s)
      character(:), allocatable :: s
      class(ScenarioDescription), intent(in) :: this

      s = this%name
   end function tostring_description

   recursive function num_fields(state, rc) result(n)
      integer :: n
      type(ESMF_State), intent(inout) :: state
      integer, optional, intent(out) :: rc

      integer :: status
      integer :: itemCount, i
      character(ESMF_MAXSTR), allocatable :: itemNameList(:)
      type(ESMF_StateItem_Flag) :: itemType
      type(ESMF_State) :: substate

      n = 0 ! default

      call ESMF_StateGet(state, itemCount=itemCount, _RC)
      allocate(itemNameList(itemCount))
      call ESMF_StateGet(state, itemNameList=itemNameList, _RC)

      do i = 1, itemCount
         call ESMF_StateGet(state, itemName=trim(itemNameList(i)), itemType=itemType, _RC)
         if (itemType == ESMF_STATEITEM_FIELD) then
            n = n + 1
         elseif (itemType == ESMF_STATEITEM_STATE) then
            call ESMF_StateGet(state, trim(itemNameList(i)), substate, _RC)
            n = n + num_fields(substate, _RC)
         end if
      end do

      return
   end function num_fields

end module Test_Scenarios