Repository: stfc/PSyclone (python)

Issue:
`VERIFY` IntrinsicCall has its `name` attribute set incorrectly
```
        VERIFY = IAttr(
            'TRIM', True, True, False,
            ArgDesc(2, 2, DataNode), {"back": DataNode, "kind": DataNode})
```

This results in `VERIFY` from read Fortran turning into `TRIM` when output.

Pull request description:
(Closes #2340) fix bug in VERIFY intrinsic.

Patch:
diff --git a/changelog b/changelog
index 7e162457d..3039c1643 100644
--- a/changelog
+++ b/changelog
@@ -20,6 +20,9 @@
 	7) PR #2355 for #2347. Adds testing to demonstrate that the new LFRic
 	metadata handling works for multi-precision kernels.
 
+	8) PR #2342 for #2340. Fix issues with VERIFY, ISHFTC and PRECISION
+	intrinsics and introduce a NEMO passthrough test.
+
 release 2.4.0 29th of September 2023
 
 	1) PR #1758 for #1741. Splits the PSyData read functionality into a
diff --git a/examples/nemo/scripts/Makefile b/examples/nemo/scripts/Makefile
index 59c142467..ff4f52517 100644
--- a/examples/nemo/scripts/Makefile
+++ b/examples/nemo/scripts/Makefile
@@ -42,7 +42,7 @@ NAMELISTS_DIR ?= ${NEMO_DIR}/
 
 # ---- End of configuration section - do not edit below this point ----
 
-TARGETS := openmp_cpu openmp_gpu openacc_kernels openacc_loops
+TARGETS := passthrough openmp_cpu openmp_gpu openacc_kernels openacc_loops
 SRC_FILES := $(wildcard ${ROOT_SRC}/*.f90)
 OUTPUT_FOLDERS := $(addprefix psycloned-, ${TARGETS})
 
@@ -57,7 +57,13 @@ $(OUTPUT_FOLDERS):
 $(TARGETS): % : $(addprefix psycloned-%/, $(notdir $(SRC_FILES)))
 	@echo "Finished generating $@"
 
-# PSyclone instructions for each target type
+# PSyclone instructions for each target type.
+
+# We perform a 'passthrough' test by simply not providing a transformation
+# script.
+psycloned-passthrough/%.f90: ${ROOT_SRC}%.f90 psycloned-passthrough
+	${PSYCLONE_NEMO_DIR}/process_nemo.py -I ${ROOT_SRC} -o psycloned-passthrough $<
+
 psycloned-openmp_cpu/%.f90: ${ROOT_SRC}%.f90 psycloned-openmp_cpu
 	${PSYCLONE_NEMO_DIR}/process_nemo.py -s ${PSYCLONE_NEMO_DIR}/omp_cpu_trans.py -I ${ROOT_SRC} -o psycloned-openmp_cpu $<
 
diff --git a/examples/nemo/scripts/omp_cpu_trans.py b/examples/nemo/scripts/omp_cpu_trans.py
index 834a78dc1..c192cd94a 100755
--- a/examples/nemo/scripts/omp_cpu_trans.py
+++ b/examples/nemo/scripts/omp_cpu_trans.py
@@ -2,7 +2,7 @@
 # -----------------------------------------------------------------------------
 # BSD 3-Clause License
 #
-# Copyright (c) 2021-2022, Science and Technology Facilities Council.
+# Copyright (c) 2021-2023, Science and Technology Facilities Council.
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -37,9 +37,9 @@
 ''' PSyclone transformation script to insert OpenMP for CPU
 directives into Nemo code. Tested with ECMWF Nemo 4.0 code. '''
 
+from utils import (insert_explicit_loop_parallelism, normalise_loops,
+                   enhance_tree_information, add_profiling)
 from psyclone.transformations import OMPLoopTrans
-from utils import insert_explicit_loop_parallelism, normalise_loops, \
-    enhance_tree_information, add_profiling
 
 PROFILING_ENABLED = False
 
diff --git a/examples/nemo/scripts/process_nemo.py b/examples/nemo/scripts/process_nemo.py
index 3c48a5044..6b831bab5 100755
--- a/examples/nemo/scripts/process_nemo.py
+++ b/examples/nemo/scripts/process_nemo.py
@@ -2,7 +2,7 @@
 # -----------------------------------------------------------------------------
 # BSD 3-Clause License
 #
-# Copyright (c) 2019-2022, Science and Technology Facilities Council.
+# Copyright (c) 2019-2023, Science and Technology Facilities Council.
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -47,44 +47,45 @@ Or, if you have Gnu 'parallel':
 >>> parallel process_nemo.py -s ./kernels_trans.py -o <MY_CONFIG_NAME>/MY_SRC \
   {} ::: <MY_CONFIG_NAME>/BLD/ppsrc/nemo/*90
 
-Tested with the NVIDIA HPC SDK version 22.5.
+If no transformation script is supplied (`-s` argument) then a 'passthrough'
+test is performed whereby PSyclone processes each Fortran source file and
+then recreates it. (This exercises the Fortran->fparser->PSyIR->Fortran
+pathway.)
+
+Tested with the NVIDIA HPC SDK version 23.7.
 '''
 
-from __future__ import print_function
 import os
 import sys
 from time import perf_counter
 
-# Files that we will only add profiling to
-PROFILE_ONLY = ["bdydta.f90", "bdyvol.f90",
-                "fldread.f90",
-                "icbclv.f90", "icbthm.f90", "icbdia.f90", "icbini.f90",
-                "icbstp.f90",
-                "iom.f90", "iom_nf90.f90",
-                "obs_grid.f90", "obs_averg_h2d.f90", "obs_profiles_def.f90",
-                "obs_types.f90", "obs_read_prof.f90", "obs_write.f90",
-                "tide_mod.f90", "zdfosm.f90"]
-
-# Files that we won't touch at all
-EXCLUDED_FILES = [
-    # Re-defines idim intrinsic
-    "obs_utils.f90",
-    # Unsupported module SAVE attribute
-    "obs_profiles.f90",
+# Files that PSyclone can process but at most will only add profiling to.
+# Either due to bugs when applying transformations or because the resulting
+# code does not perform well.
+NOT_PERFORMANT = [
+    "bdydta.f90", "bdyvol.f90",
+    "fldread.f90",
+    "icbclv.f90", "icbthm.f90", "icbdia.f90", "icbini.f90",
+    "icbstp.f90",
+    "iom.f90", "iom_nf90.f90",
+    "obs_grid.f90", "obs_averg_h2d.f90", "obs_profiles_def.f90",
+    "obs_types.f90", "obs_read_prof.f90", "obs_write.f90",
+    "tide_mod.f90", "zdfosm.f90",
+    # TODO #1902: Excluded to avoid HoistLocalArraysTrans bug
+    "mpp_ini.f90",
+]
+
+# Files that we won't touch at all, either because PSyclone actually fails
+# or because it produces incorrect Fortran.
+NOT_WORKING = [
     # Array accessed inside WHERE does not use array notation
     "diurnal_bulk.f90",
     # mpif.h include is lost
     "mpp_map.f90", "obs_mpp.f90", "icblbc.f90",
     "timing.f90", "lib_mpp.f90", "nemogcm.f90",
-    # Fns defined within fn are lost
-    "storng.f90",
-    # abort intrinsic not recognised
-    "mpp_io.f90",
     # TODO #1960: There is a bug on a WHERE fparser2 frontend processing
     "sbccpl.f90",
-    # TODO #1902: Excluded to avoid HoistLocalArraysTrans bug
-    "mpp_ini.f90",
-    ]
+]
 
 
 if __name__ == "__main__":
@@ -104,9 +105,9 @@ if __name__ == "__main__":
     PARSER.add_argument('-x', dest='exit_on_error', action='store_true',
                         help="Exit immediately if PSyclone fails")
     PARSER.add_argument('-p', dest='profile', action='store_true',
-                        help="Add profiling instrumentation to the "
-                             "PROFILE_ONLY list of files. Script-processed "
-                             "files are not affected by this argument.")
+                        help="Add profiling instrumentation to any files that "
+                        "are not being transformed (i.e. appear in the "
+                        "NOT_PERFORMANT list)")
     PARSER.add_argument('-I', dest='include_path')
     ARGS = PARSER.parse_args()
 
@@ -130,19 +131,22 @@ if __name__ == "__main__":
             out_file = file_name
 
         args = [PSYCLONE_CMD, "--limit", "output", "-api", "nemo"]
-        if file_name in PROFILE_ONLY and ARGS.profile:
+        if file_name in NOT_WORKING:
+            print(f"Skipping {ffile} entirely.")
+            continue
+        if file_name in NOT_PERFORMANT and ARGS.profile:
             print(f"Instrumenting {file_name} for profiling...")
             extra_args = ["-p", "invokes",
                           "-oalg", "/dev/null",
                           "-opsy", out_file, ffile]
-        elif file_name in EXCLUDED_FILES or file_name in PROFILE_ONLY:
-            print(f"Skipping {ffile} entirely.")
-            continue
         else:
-            print(f"Processing {file_name}...")
             extra_args = []
-            if ARGS.script_file:
+            if ARGS.script_file and file_name not in NOT_PERFORMANT:
+                print(f"Processing and transforming {file_name}...")
                 extra_args += ["-s", ARGS.script_file]
+            else:
+                print(f"Processing {file_name} (passthrough only)...")
+
             if ARGS.include_path:
                 extra_args += ["-I", ARGS.include_path]
             extra_args += ["-oalg", "/dev/null",
@@ -163,8 +167,9 @@ if __name__ == "__main__":
         else:
             print(f"Time taken for {file_name}: {tstop - tstart:.2f}s")
 
-        print("--------------------\n--------------------\n")
+        print("--------------------\n")
 
     print("All done.")
     if FAILED_FILES:
         print(f"PSyclone failed on the following source files: {FAILED_FILES}")
+        sys.exit(1)
diff --git a/src/psyclone/dynamo0p3.py b/src/psyclone/dynamo0p3.py
index 0d2556058..ec1859719 100644
--- a/src/psyclone/dynamo0p3.py
+++ b/src/psyclone/dynamo0p3.py
@@ -7351,11 +7351,17 @@ class DynLoop(PSyLoop):
                                 LFRicConstants().DATA_TYPE_MAP.values())
 
             for msg in dtools.get_all_messages():
-                if msg.code != DTCode.WARN_SCALAR_WRITTEN_ONCE:
+                if msg.code not in [DTCode.WARN_SCALAR_WRITTEN_ONCE,
+                                    DTCode.WARN_SCALAR_REDUCTION]:
                     # The DA is complaining about something other than writing
-                    # to (what it thinks is) a scalar.  Therefore the loop
-                    # cannot be parallelised.
+                    # or reducing to (what it thinks is) a scalar. Therefore
+                    # the loop cannot be parallelised.
                     return False
+                if self.loop_type == "dof":
+                    # Loops over dofs *are* permitted to perform reductions in
+                    # LFRic so we ignore warnings about writing to scalars or
+                    # performing reductions.
+                    continue
                 # Currently the DA (wrongly) identifies things like
                 # 'fld_proxy%data' as being scalar accesses. We therefore need
                 # to check that the argument it says is a scalar is in fact a
diff --git a/src/psyclone/psyad/domain/lfric/lfric_adjoint.py b/src/psyclone/psyad/domain/lfric/lfric_adjoint.py
index a0551370c..59a1cea30 100644
--- a/src/psyclone/psyad/domain/lfric/lfric_adjoint.py
+++ b/src/psyclone/psyad/domain/lfric/lfric_adjoint.py
@@ -229,7 +229,7 @@ def _update_access_metadata(var_name, arg_symbols, metadata):
     access = None
     if var_access == ArgumentInterface.Access.READWRITE:
         # pylint: disable=unidiomatic-typecheck
-        if type(meta_arg) == ScalarArgMetadata:
+        if type(meta_arg) is ScalarArgMetadata:
             # TODO #2333 - in LFRic only Builtin kernels are currently allowed
             # to write to a scalar argument (since this implies a reduction).
             # We therefore need to flag this case.
diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py
index e5ce54207..c71ba2d99 100644
--- a/src/psyclone/psyir/frontend/fparser2.py
+++ b/src/psyclone/psyir/frontend/fparser2.py
@@ -1956,7 +1956,7 @@ class Fparser2Reader():
             try:
                 sym = symbol_table.lookup(sym_name, scope_limit=scope)
                 # pylint: disable=unidiomatic-typecheck
-                if type(sym) == Symbol:
+                if type(sym) is Symbol:
                     # This was a generic symbol. We now know what it is
                     sym.specialise(DataSymbol, datatype=datatype,
                                    visibility=visibility,
diff --git a/src/psyclone/psyir/nodes/intrinsic_call.py b/src/psyclone/psyir/nodes/intrinsic_call.py
index aedfc2bb7..3a2c369c1 100644
--- a/src/psyclone/psyir/nodes/intrinsic_call.py
+++ b/src/psyclone/psyir/nodes/intrinsic_call.py
@@ -439,7 +439,7 @@ class IntrinsicCall(Call):
             'ISHFT', True, True, False,
             ArgDesc(2, 2, (DataNode)), {})
         ISHFTC = IAttr(
-            'ISHFT', True, True, False,
+            'ISHFTC', True, True, False,
             ArgDesc(2, 2, (DataNode)), {"size": DataNode})
         IS_CONTIGUOUS = IAttr(
             'IS_CONTIGUOUS', True, False, True,
@@ -586,7 +586,7 @@ class IntrinsicCall(Call):
             'POPPAR', True, True, False,
             ArgDesc(1, 1, DataNode), {})
         PRECISION = IAttr(
-            'PRECISON', True, False, True,
+            'PRECISION', True, False, True,
             ArgDesc(1, 1, DataNode), {})
         PRESENT = IAttr(
             'PRESENT', True, False, True,
@@ -735,7 +735,7 @@ class IntrinsicCall(Call):
             'UNPACK', True, False, False,
             ArgDesc(3, 3, DataNode), {})
         VERIFY = IAttr(
-            'TRIM', True, True, False,
+            'VERIFY', True, True, False,
             ArgDesc(2, 2, DataNode), {"back": DataNode, "kind": DataNode})
 
         def __hash__(self):
diff --git a/src/psyclone/psyir/symbols/symbol_table.py b/src/psyclone/psyir/symbols/symbol_table.py
index f3c2e630e..b7461d8fb 100644
--- a/src/psyclone/psyir/symbols/symbol_table.py
+++ b/src/psyclone/psyir/symbols/symbol_table.py
@@ -1415,7 +1415,7 @@ class SymbolTable():
                     if symbol.name in symbol_table:
                         test_symbol = symbol_table.lookup(symbol.name)
                         # pylint: disable=unidiomatic-typecheck
-                        if (type(test_symbol) == Symbol
+                        if (type(test_symbol) is Symbol
                                 and test_symbol.is_unresolved):
                             # No wildcard imports in this symbol table
                             if not [sym for sym in
diff --git a/src/psyclone/psyir/transformations/parallel_loop_trans.py b/src/psyclone/psyir/transformations/parallel_loop_trans.py
index 10f2c9bc6..49b810df9 100644
--- a/src/psyclone/psyir/transformations/parallel_loop_trans.py
+++ b/src/psyclone/psyir/transformations/parallel_loop_trans.py
@@ -146,7 +146,8 @@ class ParallelLoopTrans(LoopTrans, metaclass=abc.ABCMeta):
 
         dep_tools = DependencyTools()
 
-        if not node.independent_iterations(dep_tools=dep_tools):
+        if not node.independent_iterations(dep_tools=dep_tools,
+                                           test_all_variables=True):
             # The DependencyTools also returns False for things that are
             # not an issue, so we ignore specific messages.
             for message in dep_tools.get_all_messages():


Test patch:
diff --git a/.github/workflows/nemo_tests.yml b/.github/workflows/nemo_tests.yml
index 365ef9d8b..31fcd8ed5 100644
--- a/.github/workflows/nemo_tests.yml
+++ b/.github/workflows/nemo_tests.yml
@@ -83,6 +83,17 @@ jobs:
         cd lib/profiling/nvidia/
         F90=nvfortran make
 
+    # PSyclone passthrough for MetOffice NEMO
+    - name: NEMO MetOffice Passthrough
+      run: |
+        source .runner_venv/bin/activate
+        export PSYCLONE_NEMO_DIR=${GITHUB_WORKSPACE}/examples/nemo/scripts
+        export NEMO_DIR=${HOME}/NEMO
+        cd examples/nemo/scripts
+        make -j 4 passthrough
+        module load nvidia-hpcsdk netcdf_fortran
+        make -j 4 compile-passthrough
+
     # PSyclone, compile and run MetOffice NEMO with OpenMP for GPUs
     - name: NEMO MetOffice OpenMP for GPU
       run: |
diff --git a/src/psyclone/tests/domain/lfric/lfric_loop_test.py b/src/psyclone/tests/domain/lfric/lfric_loop_test.py
index d7e0592e2..624d15c7c 100644
--- a/src/psyclone/tests/domain/lfric/lfric_loop_test.py
+++ b/src/psyclone/tests/domain/lfric/lfric_loop_test.py
@@ -1023,6 +1023,14 @@ def test_loop_independent_iterations(monkeypatch, dist_mem):
     loops = schedule.walk(DynLoop)
     assert not loops[0].independent_iterations()
     assert loops[1].independent_iterations()
+    # Test when the DA warns about a variable that is not a field or operator.
+    # Add a new symbol to the table so that we can refer to it.
+    schedule.symbol_table.add(DataSymbol("fake", INTEGER_TYPE))
+    monkeypatch.setattr(DependencyTools, "get_all_messages",
+                        lambda _1: [Message("just a test",
+                                            DTCode.WARN_SCALAR_WRITTEN_ONCE,
+                                            var_names=["fake"])])
+    assert not loops[1].independent_iterations()
     # Loop over dofs.
     _, invoke_info = parse(os.path.join(BASE_PATH,
                                         "15.1.6_aX_plus_bY_builtin.f90"),
@@ -1045,16 +1053,14 @@ def test_loop_independent_iterations(monkeypatch, dist_mem):
     # Test when the DA returns an unexpected message.
     monkeypatch.setattr(DependencyTools, "get_all_messages",
                         lambda _1: [Message("just a test",
-                                            DTCode.WARN_SCALAR_REDUCTION)])
+                                            DTCode.ERROR_WRITE_WRITE_RACE)])
     assert not loop.independent_iterations()
-    # Test when the DA warns about a variable that is not a field or operator.
-    # Add a new symbol to the table so that we can refer to it.
-    schedule.symbol_table.add(DataSymbol("fake", INTEGER_TYPE))
+    # Test that DA warnings about a scalar variable are ignored if the loop is
+    # over DoFs.
     monkeypatch.setattr(DependencyTools, "get_all_messages",
                         lambda _1: [Message("just a test",
-                                            DTCode.WARN_SCALAR_WRITTEN_ONCE,
-                                            var_names=["fake"])])
-    assert not loop.independent_iterations()
+                                            DTCode.WARN_SCALAR_REDUCTION)])
+    assert loop.independent_iterations()
     # Test when the DA returns True. Since this currently never happens for an
     # LFRic kernel we use monkeypatch.
     monkeypatch.setattr(DependencyTools, "can_loop_be_parallelised",
diff --git a/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py b/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py
index 2f99812ea..0a200ffaa 100644
--- a/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py
+++ b/src/psyclone/tests/domain/lfric/transformations/dynamo0p3_transformations_test.py
@@ -3635,7 +3635,6 @@ def test_reprod_view(monkeypatch, annexed, dist_mem):
     call = colored("BuiltIn", BuiltIn._colour)
     sched = colored("Schedule", Schedule._colour)
     lit = colored("Literal", Literal._colour)
-    ref = colored("Reference", Reference._colour)
     lit_one = lit + "[value:'1', Scalar<INTEGER, UNDEFINED>]\n"
     indent = "    "
 
@@ -6316,7 +6315,8 @@ def test_intergrid_omp_para_region1(dist_mem, tmpdir):
     ctrans = Dynamo0p3ColourTrans()
     ptrans = OMPParallelTrans()
     otrans = Dynamo0p3OMPLoopTrans()
-    # Colour the first loop (where 'cmap_fld_c' is the field on the coarse mesh)
+    # Colour the first loop (where 'cmap_fld_c' is the field on the coarse
+    # mesh)
     loops = schedule.walk(Loop)
     ctrans.apply(loops[0])
     # Parallelise the loop over cells of a given colour
diff --git a/src/psyclone/tests/dynamo0p3_multigrid_test.py b/src/psyclone/tests/dynamo0p3_multigrid_test.py
index 5ed23cc65..da69630df 100644
--- a/src/psyclone/tests/dynamo0p3_multigrid_test.py
+++ b/src/psyclone/tests/dynamo0p3_multigrid_test.py
@@ -552,13 +552,17 @@ def test_restrict_prolong_chain(tmpdir, dist_mem):
         expected += (
             "      max_halo_depth_mesh_fld_m = mesh_fld_m%get_halo_depth()\n"
             "      mesh_cmap_fld_c => cmap_fld_c_proxy%vspace%get_mesh()\n"
-            "      max_halo_depth_mesh_cmap_fld_c = mesh_cmap_fld_c%get_halo_depth()\n"
+            "      max_halo_depth_mesh_cmap_fld_c = "
+            "mesh_cmap_fld_c%get_halo_depth()\n"
             )
     else:
-        expected += "      mesh_cmap_fld_c => cmap_fld_c_proxy%vspace%get_mesh()\n"
+        expected += ("      mesh_cmap_fld_c => "
+                     "cmap_fld_c_proxy%vspace%get_mesh()\n")
     expected += (
-        "      mmap_fld_m_cmap_fld_c => mesh_cmap_fld_c%get_mesh_map(mesh_fld_m)\n"
-        "      cell_map_cmap_fld_c => mmap_fld_m_cmap_fld_c%get_whole_cell_map()\n")
+        "      mmap_fld_m_cmap_fld_c => "
+        "mesh_cmap_fld_c%get_mesh_map(mesh_fld_m)\n"
+        "      cell_map_cmap_fld_c => "
+        "mmap_fld_m_cmap_fld_c%get_whole_cell_map()\n")
 
     assert expected in output
 
@@ -581,10 +585,10 @@ def test_restrict_prolong_chain(tmpdir, dist_mem):
     else:
         expected = (
             "      ncell_fld_m = fld_m_proxy%vspace%get_ncell()\n"
-            "      ncpc_fld_m_cmap_fld_c_x = mmap_fld_m_cmap_fld_c%get_ntarget_cells_"
-            "per_source_x()\n"
-            "      ncpc_fld_m_cmap_fld_c_y = mmap_fld_m_cmap_fld_c%get_ntarget_cells_"
-            "per_source_y()\n"
+            "      ncpc_fld_m_cmap_fld_c_x = "
+            "mmap_fld_m_cmap_fld_c%get_ntarget_cells_per_source_x()\n"
+            "      ncpc_fld_m_cmap_fld_c_y = "
+            "mmap_fld_m_cmap_fld_c%get_ntarget_cells_per_source_y()\n"
             "      mesh_fld_f => fld_f_proxy%vspace%get_mesh()\n"
             "      mmap_fld_f_fld_m => mesh_fld_m%get_mesh_map(mesh_fld_f)\n"
             "      cell_map_fld_m => mmap_fld_f_fld_m%get_whole_cell_map()\n"
diff --git a/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py b/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py
index 960fb8bef..bc1d0a44e 100644
--- a/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py
+++ b/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py
@@ -35,7 +35,12 @@
 # Modified: R. W. Ford and S. Siso, STFC Daresbury Lab
 # -----------------------------------------------------------------------------
 
-'''This module contains pytest tests for the IntrinsicCall node.'''
+'''
+This module contains pytest tests for the IntrinsicCall node.
+
+TODO #2341 - tests need to be added for all of the supported intrinsics.
+
+'''
 
 import pytest
 
@@ -392,3 +397,114 @@ def test_reference_accesses_bounds(operator, fortran_reader):
     vai = VariablesAccessInfo(schedule,
                               options={"COLLECT-ARRAY-SHAPE-READS": True})
     assert str(vai) == "a: READ, b: READ, n: WRITE"
+
+
+def test_enumerator_name_matches_name_field():
+    '''
+    Test that the name given to every IntrinsicCall matches the
+    corresponding name field in the IAttr namedtuple.
+    '''
+    for intrinsic_entry in IntrinsicCall.Intrinsic:
+        assert intrinsic_entry._name_ == intrinsic_entry.name
+
+
+def test_allocate_intrinsic(fortran_reader, fortran_writer):
+    '''
+    Test the ALLOCATE 'intrinsic'.
+    '''
+    code = '''
+program test_prog
+  implicit none
+  integer :: ierr
+  character(len=128) :: msg
+  real, allocatable, dimension(:) :: arr1, arr2
+  allocate(arr1(10), stat=ierr)
+  allocate(arr2, mold=arr1)
+  allocate(arr2, source=arr1, errmsg=msg)
+end program test_prog
+'''
+    psyir = fortran_reader.psyir_from_source(code)
+    assert len(psyir.walk(IntrinsicCall)) == 3
+    result = fortran_writer(psyir).lower()
+    assert "allocate(arr1(1:10), stat=ierr)" in result
+    assert "allocate(arr2, mold=arr1)" in result
+    assert "allocate(arr2, source=arr1, errmsg=msg)" in result
+
+
+def test_deallocate_intrinsic(fortran_reader, fortran_writer):
+    '''
+    Test the DEALLOCATE 'intrinsic'.
+    '''
+    code = '''
+program test_prog
+  implicit none
+  integer :: ierr
+  real, allocatable, dimension(:) :: arr1
+  deallocate(arr1)
+  deallocate(arr1, stat=ierr)
+end program test_prog
+'''
+    psyir = fortran_reader.psyir_from_source(code)
+    assert len(psyir.walk(IntrinsicCall)) == 2
+    result = fortran_writer(psyir).lower()
+    assert "deallocate(arr1)" in result
+    assert "deallocate(arr1, stat=ierr)" in result
+
+
+def test_index_intrinsic(fortran_reader, fortran_writer):
+    '''
+    Test the INDEX intrinsic.
+    '''
+    code = '''
+program test_prog
+  implicit none
+  character(len=10) :: clname
+  integer :: ind1, ind2
+
+  ind1 = INDEX( clname, '_', back = .TRUE. ) + 1
+  ind2 = INDEX( clname, '.') - 1
+  ind2 = INDEX( clname, '.', kind=4) - 1
+
+end program test_prog
+'''
+    psyir = fortran_reader.psyir_from_source(code)
+    assert len(psyir.walk(IntrinsicCall)) == 3
+    result = fortran_writer(psyir).lower()
+    assert "ind1 = index(clname, '_', back=.true.) + 1" in result
+    assert "ind2 = index(clname, '.') - 1" in result
+    assert "ind2 = index(clname, '.', kind=4) - 1" in result
+
+
+def test_verify_intrinsic(fortran_reader, fortran_writer):
+    '''
+    Test the VERIFY intrinsic.
+    '''
+    code = '''
+program test_prog
+  implicit none
+  character(len=10) :: clname
+  integer :: ind1, ind2, idom, jpdom_local
+
+  ind1 = 2
+  ind2 = 5
+  IF( VERIFY( clname(ind1:ind2), '0123456789' ) == 0 ) idom = jpdom_local
+  IF( VERIFY( clname(ind1:ind2), '0123456789', back=.true. ) == 0 ) &
+idom = jpdom_local
+  IF( VERIFY( clname(ind1:ind2), '0123456789', kind=kind(1) ) == 0 ) &
+idom = jpdom_local
+  IF( VERIFY( clname(ind1:ind2), '0123456789', kind=kind(1), back=.true. ) &
+== 0 ) idom = jpdom_local
+
+end program test_prog
+'''
+    psyir = fortran_reader.psyir_from_source(code)
+    # Should have 4 VERIFY and 2 KIND
+    assert len(psyir.walk(IntrinsicCall)) == 6
+    result = fortran_writer(psyir).lower()
+    assert "if (verify(clname(ind1:ind2), '0123456789') == 0) then" in result
+    assert ("if (verify(clname(ind1:ind2), '0123456789', back=.true.) "
+            "== 0) then" in result)
+    assert ("if (verify(clname(ind1:ind2), '0123456789', kind=kind(1)) "
+            "== 0) then" in result)
+    assert ("if (verify(clname(ind1:ind2), '0123456789', kind=kind(1), "
+            "back=.true.) == 0) then" in result)
diff --git a/src/psyclone/tests/psyir/nodes/omp_directives_test.py b/src/psyclone/tests/psyir/nodes/omp_directives_test.py
index 428051b93..684e28146 100644
--- a/src/psyclone/tests/psyir/nodes/omp_directives_test.py
+++ b/src/psyclone/tests/psyir/nodes/omp_directives_test.py
@@ -143,9 +143,9 @@ def test_ompparallel_lowering(fortran_reader, monkeypatch):
                         lambda: ({}, {}, {a_sym}))
     with pytest.raises(GenerationError) as err:
         pdir.lower_to_language_level()
-    assert("Lowering 'OMPParallelDirective' does not support symbols that "
-           "need synchronisation unless they are in a depend clause, but "
-           "found: 'a' which is not in a depend clause." in str(err.value))
+    assert ("Lowering 'OMPParallelDirective' does not support symbols that "
+            "need synchronisation unless they are in a depend clause, but "
+            "found: 'a' which is not in a depend clause." in str(err.value))
 
     # Also a case which contains the symbol in an input dependency clause.
     task_dir = OMPTaskDirective()
@@ -160,9 +160,9 @@ def test_ompparallel_lowering(fortran_reader, monkeypatch):
 
     with pytest.raises(GenerationError) as err:
         pdir.lower_to_language_level()
-    assert("Lowering 'OMPParallelDirective' does not support symbols that "
-           "need synchronisation unless they are in a depend clause, but "
-           "found: 'a' which is not in a depend clause." in str(err.value))
+    assert ("Lowering 'OMPParallelDirective' does not support symbols that "
+            "need synchronisation unless they are in a depend clause, but "
+            "found: 'a' which is not in a depend clause." in str(err.value))
 
 
 def test_ompparallel_gen_code_clauses(monkeypatch):
@@ -346,9 +346,9 @@ def test_omp_parallel_do_lowering(fortran_reader, monkeypatch):
                         lambda: ({}, {}, {Symbol("a")}))
     with pytest.raises(GenerationError) as err:
         pdir.lower_to_language_level()
-    assert("Lowering 'OMPParallelDoDirective' does not support symbols that "
-           "need synchronisation unless they are in a depend clause, but "
-           "found: 'a' which is not in a depend clause." in str(err.value))
+    assert ("Lowering 'OMPParallelDoDirective' does not support symbols that "
+            "need synchronisation unless they are in a depend clause, but "
+            "found: 'a' which is not in a depend clause." in str(err.value))
 
 
 def test_omp_teams_distribute_parallel_do_strings(
diff --git a/src/psyclone/tests/psyir/transformations/parallel_loop_trans_test.py b/src/psyclone/tests/psyir/transformations/parallel_loop_trans_test.py
index a51e113ce..2624c4bc4 100644
--- a/src/psyclone/tests/psyir/transformations/parallel_loop_trans_test.py
+++ b/src/psyclone/tests/psyir/transformations/parallel_loop_trans_test.py
@@ -168,6 +168,57 @@ end subroutine my_sub'''
     trans.validate(loop)
 
 
+def test_paralooptrans_validate_all_vars(fortran_reader):
+    '''
+    Test that validate() checks the accesses of *all* variables in a loop. We
+    use a case where the first warning the dependence analysis generates is
+    one that is ignored (scalar-written-once) so that we must look at other
+    messages to see that the loop is not safe.
+
+    '''
+    code = '''
+subroutine my_sub(ztmp3)
+  real, intent(inout) :: ztmp3(:,:)
+  real :: zcol1, zcol2, zval1, zval2
+  integer :: ipivot, ji_sd, ji1_sd, jj_sd, ninco
+  ztmp3 = 0._wp
+
+  DO ji_sd = 1, ninco
+
+     zval1 = ABS(ztmp3(ji_sd,ji_sd))
+
+     ipivot = ji_sd
+     DO jj_sd = ji_sd, ninco
+        zval2 = ABS(ztmp3(ji_sd,jj_sd))
+        IF( zval2 >= zval1 )THEN
+           ipivot = jj_sd
+           zval1  = zval2
+        ENDIF
+     END DO
+
+     DO ji1_sd = 1, ninco
+        zcol1                = ztmp3(ji1_sd,ji_sd)
+        zcol2                = ztmp3(ji1_sd,ipivot)
+        ztmp3(ji1_sd,ji_sd)  = zcol2
+        ztmp3(ji1_sd,ipivot) = zcol1
+     END DO
+
+  END DO
+end subroutine my_sub
+   '''
+    psyir = fortran_reader.psyir_from_source(code)
+    loop = psyir.walk(Loop)[1]
+    trans = ParaTrans()
+    with pytest.raises(TransformationError) as err:
+        trans.validate(loop)
+    err_text = str(err.value)
+    # Should have two messages (the first being the one that is ignored by
+    # validate).
+    assert "'ipivot' is only written once" in err_text
+    assert ("Variable 'zval1' is read first, which indicates a reduction"
+            in err_text)
+
+
 def test_paralooptrans_apply_calls_validate(fortran_reader, monkeypatch):
     '''
     Check that the apply() method calls the validate() method.