if(NOT DEFINED TEST_NP)
  include(ProcessorCount)
  ProcessorCount(NP)
  math(EXPR TEST_NP "${NP}/2 + 1")
endif()

if(EXISTS ${MPIEXEC})
  # OpenMPI 3.0 and higher checks the number of processes against the number of CPUs
  execute_process(COMMAND ${MPIEXEC} --version RESULT_VARIABLE mpi_version_result OUTPUT_VARIABLE mpi_version_output ERROR_VARIABLE mpi_version_output)
  if (mpi_version_result EQUAL 0 AND mpi_version_output MATCHES "\\(Open(RTE| MPI)\\) ([3-9]\\.|1[0-9])")
    set(MPIEXEC_OVERSUBSCRIBE "-oversubscribe")
  else()
    set(MPIEXEC_OVERSUBSCRIBE "")
  endif()
endif()

function(PYTHON_TEST)
  cmake_parse_arguments(TEST "" "FILE;MAX_NUM_PROC;SUFFIX" "DEPENDS;DEPENDENCIES;LABELS" ${ARGN})
  get_filename_component(TEST_NAME ${TEST_FILE} NAME_WE)
  if(TEST_SUFFIX)
    set(TEST_NAME "${TEST_NAME}_${TEST_SUFFIX}")
  endif()
  set(TEST_FILE_CONFIGURED "${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}.py")
  configure_file(${TEST_FILE} ${TEST_FILE_CONFIGURED})
  foreach(dependency IN LISTS TEST_DEPENDENCIES)
      configure_file(${dependency} ${CMAKE_CURRENT_BINARY_DIR}/${dependency})
  endforeach(dependency)
  set(TEST_FILE ${TEST_FILE_CONFIGURED})

  if(NOT DEFINED TEST_MAX_NUM_PROC)
    set(TEST_MAX_NUM_PROC ${TEST_NP})
  endif()

  if("${TEST_MAX_NUM_PROC}" GREATER ${TEST_NP})
    set(TEST_NUM_PROC ${TEST_NP})
   else()
    set(TEST_NUM_PROC ${TEST_MAX_NUM_PROC})
  endif()

  if(EXISTS ${MPIEXEC})
    add_test(NAME ${TEST_NAME}
            COMMAND
            ${MPIEXEC} ${MPIEXEC_OVERSUBSCRIBE} ${MPIEXEC_NUMPROC_FLAG} ${TEST_NUM_PROC} ${CMAKE_BINARY_DIR}/pypresso ${TEST_FILE}
            )
  else()
    add_test(${TEST_NAME} ${CMAKE_BINARY_DIR}/pypresso ${TEST_FILE})
  endif()
  set_tests_properties(${TEST_NAME} PROPERTIES PROCESSORS ${TEST_NUM_PROC} DEPENDS "${TEST_DEPENDS}")

 if("gpu" IN_LIST TEST_LABELS)
   set_tests_properties(${TEST_NAME} PROPERTIES RUN_SERIAL ON)
 endif()

  if(${TEST_MAX_NUM_PROC} LESS 2)
    set_tests_properties(${TEST_NAME} PROPERTIES LABELS "${TEST_LABELS}")
  elseif(${TEST_MAX_NUM_PROC} LESS 3)
    set_tests_properties(${TEST_NAME} PROPERTIES LABELS "${TEST_LABELS};parallel")
  else()
    set_tests_properties(${TEST_NAME} PROPERTIES LABELS "${TEST_LABELS};parallel;parallel_odd")
  endif()

  set(python_tests ${python_tests} ${TEST_FILE} PARENT_SCOPE)
endfunction(PYTHON_TEST)

# Checkpointing tests: semicolon-separated list of mutually-compatible features.
# Separate features with hyphens, use a period to add an optional flag.
foreach(TEST_COMBINATION lb.cpu-p3m.cpu-lj-lbtherm;lb.gpu-p3m.cpu-lj-lvtherm;ek.gpu)
  if(${TEST_COMBINATION} MATCHES "\\.gpu")
    set(TEST_LABELS "gpu")
  else()
    set(TEST_LABELS "")
  endif()
  foreach(TEST_BINARY 1;0)
    python_test(FILE save_checkpoint.py MAX_NUM_PROC 4 LABELS ${TEST_LABELS} SUFFIX ${TEST_COMBINATION}_${TEST_BINARY})
    python_test(FILE test_checkpoint.py MAX_NUM_PROC 4 LABELS ${TEST_LABELS} SUFFIX ${TEST_COMBINATION}_${TEST_BINARY}
                DEPENDS save_checkpoint_${TEST_COMBINATION}_${TEST_BINARY})
  endforeach(TEST_BINARY)
endforeach(TEST_COMBINATION)
python_test(FILE cellsystem.py MAX_NUM_PROC 4)
python_test(FILE tune_skin.py MAX_NUM_PROC 1)
python_test(FILE constraint_homogeneous_magnetic_field.py MAX_NUM_PROC 4)
python_test(FILE constraint_shape_based.py MAX_NUM_PROC 2)
python_test(FILE coulomb_cloud_wall.py MAX_NUM_PROC 4 LABELS gpu)
python_test(FILE coulomb_tuning.py MAX_NUM_PROC 4 LABELS gpu)
python_test(FILE correlation.py MAX_NUM_PROC 4)
python_test(FILE dawaanr-and-dds-gpu.py MAX_NUM_PROC 1 LABELS gpu)
python_test(FILE dawaanr-and-bh-gpu.py MAX_NUM_PROC 1 LABELS gpu)
python_test(FILE dds-and-bh-gpu.py MAX_NUM_PROC 4 LABELS gpu)
python_test(FILE electrostaticInteractions.py MAX_NUM_PROC 2)
python_test(FILE engine_langevin.py MAX_NUM_PROC 4)
python_test(FILE engine_lb.py MAX_NUM_PROC 2 LABELS gpu)
python_test(FILE icc.py MAX_NUM_PROC 4)
python_test(FILE magnetostaticInteractions.py MAX_NUM_PROC 1)
python_test(FILE mass-and-rinertia_per_particle.py MAX_NUM_PROC 2)
python_test(FILE integrate.py MAX_NUM_PROC 4)
python_test(FILE interactions_bond_angle.py MAX_NUM_PROC 4)
python_test(FILE interactions_bonded_interface.py MAX_NUM_PROC 4)
python_test(FILE interactions_bonded.py MAX_NUM_PROC 2)
python_test(FILE interactions_dihedral.py MAX_NUM_PROC 4)
python_test(FILE interactions_non-bonded_interface.py MAX_NUM_PROC 4)
python_test(FILE interactions_non-bonded.py MAX_NUM_PROC 4)
python_test(FILE observables.py MAX_NUM_PROC 4)
python_test(FILE p3m_gpu.py MAX_NUM_PROC 4 LABELS gpu)
python_test(FILE particle.py MAX_NUM_PROC 4)
python_test(FILE stress.py MAX_NUM_PROC 4)
python_test(FILE scafacos_dipoles_1d_2d.py MAX_NUM_PROC 4)
python_test(FILE tabulated.py MAX_NUM_PROC 2)
python_test(FILE particle_slice.py MAX_NUM_PROC 4)
python_test(FILE rigid_bond.py MAX_NUM_PROC 4)
python_test(FILE rotation_per_particle.py MAX_NUM_PROC 4)
python_test(FILE rotational_inertia.py MAX_NUM_PROC 4)
python_test(FILE rotational-diffusion-aniso.py MAX_NUM_PROC 1)
python_test(FILE script_interface_object_params.py MAX_NUM_PROC 4)
python_test(FILE reaction_ensemble.py MAX_NUM_PROC 4)
python_test(FILE widom_insertion.py MAX_NUM_PROC 1)
python_test(FILE constant_pH.py MAX_NUM_PROC 4)
python_test(FILE writevtf.py MAX_NUM_PROC 4)
python_test(FILE lb_stokes_sphere.py MAX_NUM_PROC 2 LABELS gpu long)
python_test(FILE ek_fluctuations.py MAX_NUM_PROC 1 LABELS gpu)
python_test(FILE ek_charged_plate.py MAX_NUM_PROC 1 LABELS gpu)
python_test(FILE ek_eof_one_species_x.py MAX_NUM_PROC 1 LABELS gpu)
python_test(FILE ek_eof_one_species_y.py MAX_NUM_PROC 1 LABELS gpu)
python_test(FILE ek_eof_one_species_z.py MAX_NUM_PROC 1 LABELS gpu)
python_test(FILE exclusions.py MAX_NUM_PROC 2)
python_test(FILE langevin_thermostat.py MAX_NUM_PROC 1)
python_test(FILE nsquare.py MAX_NUM_PROC 4)
python_test(FILE virtual_sites_relative.py MAX_NUM_PROC 2)
python_test(FILE virtual_sites_tracers.py MAX_NUM_PROC 2)
python_test(FILE virtual_sites_tracers_gpu.py MAX_NUM_PROC 2 LABELS gpu)
python_test(FILE domain_decomposition.py MAX_NUM_PROC 4)
python_test(FILE layered.py MAX_NUM_PROC 4)
python_test(FILE minimize_energy.py MAX_NUM_PROC 4)
python_test(FILE dipolar_mdlc_p3m_scafacos_p2nfft.py MAX_NUM_PROC 1 LABELS long)
python_test(FILE lb.py MAX_NUM_PROC 2 LABELS gpu)
python_test(FILE force_cap.py MAX_NUM_PROC 2)
python_test(FILE dpd.py MAX_NUM_PROC 4)
python_test(FILE hat.py MAX_NUM_PROC 4)
python_test(FILE analyze_energy.py MAX_NUM_PROC 2)
python_test(FILE analyze_itensor.py MAX_NUM_PROC 4)
python_test(FILE rdf.py MAX_NUM_PROC 1)
python_test(FILE coulomb_mixed_periodicity.py MAX_NUM_PROC 4 LABELS long)
python_test(FILE coulomb_cloud_wall_duplicated.py MAX_NUM_PROC 4 LABELS gpu LABELS long)
python_test(FILE collision_detection.py MAX_NUM_PROC 4)
python_test(FILE lb_get_u_at_pos.py MAX_NUM_PROC 4 LABELS gpu)
python_test(FILE lj.py MAX_NUM_PROC 4)
python_test(FILE pairs.py MAX_NUM_PROC 4)
python_test(FILE polymer.py MAX_NUM_PROC 4)
python_test(FILE auto_exclusions.py MAX_NUM_PROC 1)
python_test(FILE subt_lj.py MAX_NUM_PROC 2)
python_test(FILE observable_cylindrical.py MAX_NUM_PROC 4)
python_test(FILE observable_cylindricalLB.py MAX_NUM_PROC 1 LABELS gpu)
python_test(FILE analyze_chains.py MAX_NUM_PROC 1)
python_test(FILE analyze_distance.py MAX_NUM_PROC 1)
python_test(FILE comfixed.py MAX_NUM_PROC 2)
python_test(FILE rescale.py MAX_NUM_PROC 2)
python_test(FILE npt.py MAX_NUM_PROC 4)
python_test(FILE elc_vs_mmm2d_neutral.py MAX_NUM_PROC 2)
python_test(FILE elc_vs_mmm2d_nonneutral.py MAX_NUM_PROC 2)
python_test(FILE accumulator.py MAX_NUM_PROC 4)
python_test(FILE wang_landau_reaction_ensemble.py MAX_NUM_PROC 1 LABELS long)
python_test(FILE array_properties.py MAX_NUM_PROC 4)
python_test(FILE analyze_distribution.py MAX_NUM_PROC 1)
python_test(FILE observable_profile.py MAX_NUM_PROC 4)
python_test(FILE observable_profileLB.py MAX_NUM_PROC 1 LABELS gpu)
python_test(FILE rotate_system.py MAX_NUM_PROC 4)
python_test(FILE random_pairs.py MAX_NUM_PROC 4)
python_test(FILE lb_electrohydrodynamics.py MAX_NUM_PROC 4 LABELS gpu)
python_test(FILE cluster_analysis.py MAX_NUM_PROC 4)
python_test(FILE pair_criteria.py MAX_NUM_PROC 4)
python_test(FILE actor.py MAX_NUM_PROC 1)
python_test(FILE drude.py MAX_NUM_PROC 2)
python_test(FILE thermalized_bond.py MAX_NUM_PROC 4)
python_test(FILE thole.py MAX_NUM_PROC 4)
python_test(FILE lb_switch.py MAX_NUM_PROC 1 LABELS gpu)
python_test(FILE lb_boundary_velocity.py MAX_NUM_PROC 1)
python_test(FILE lb_thermo_virtual.py MAX_NUM_PROC 2 LABELS gpu)
python_test(FILE lb_poiseuille.py MAX_NUM_PROC 4 LABELS gpu)
python_test(FILE lb_poiseuille_cylinder.py MAX_NUM_PROC 2 LABELS gpu)
python_test(FILE lb_interpolation.py MAX_NUM_PROC 4 LABELS gpu)
python_test(FILE analyze_gyration_tensor.py MAX_NUM_PROC 1)
python_test(FILE oif_volume_conservation.py MAX_NUM_PROC 2)
python_test(FILE simple_pore.py MAX_NUM_PROC 1)
python_test(FILE field_test.py MAX_NUM_PROC 1)
python_test(FILE lb_boundary.py MAX_NUM_PROC 2 LABELS gpu)
python_test(FILE lb_streaming.py MAX_NUM_PROC 4 LABELS gpu)
python_test(FILE lb_shear.py MAX_NUM_PROC 2 LABELS gpu)
python_test(FILE lb_thermostat.py MAX_NUM_PROC 2 LABELS gpu)
python_test(FILE p3m_electrostatic_pressure.py MAX_NUM_PROC 2)
python_test(FILE sigint.py DEPENDENCIES sigint_child.py MAX_NUM_PROC 1)
python_test(FILE lb_density.py MAX_NUM_PROC 1)
python_test(FILE observable_chain.py MAX_NUM_PROC 4)
python_test(FILE mpiio.py MAX_NUM_PROC 4)
python_test(FILE gpu_availability.py MAX_NUM_PROC 1 LABELS gpu)
python_test(FILE features.py MAX_NUM_PROC 1)
python_test(FILE galilei.py MAX_NUM_PROC 32)
python_test(FILE time_series.py MAX_NUM_PROC 1)
python_test(FILE linear_momentum.py)
python_test(FILE linear_momentum_lb.py MAX_NUM_PROC 2 LABELS gpu)
python_test(FILE mmm1d.py MAX_NUM_PROC 2 LABELS gpu)
python_test(FILE lees_edwards.py MAX_NUM_PROC 2)

if(PY_H5PY)
  python_test(FILE h5md.py MAX_NUM_PROC 2)
endif(PY_H5PY)

add_custom_target(python_test_data
  COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/data ${CMAKE_CURRENT_BINARY_DIR}/data
  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/tests_common.py ${CMAKE_CURRENT_BINARY_DIR}
  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/unittest_decorators.py ${CMAKE_CURRENT_BINARY_DIR}
  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/virtual_sites_tracers_common.py ${CMAKE_CURRENT_BINARY_DIR}
  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/ek_common.py ${CMAKE_CURRENT_BINARY_DIR}
  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/ek_eof_one_species_base.py ${CMAKE_CURRENT_BINARY_DIR}
)

add_custom_target(check_python_parallel_odd COMMAND ${CMAKE_CTEST_COMMAND} --timeout ${TEST_TIMEOUT} -j${TEST_NP} -L parallel_odd $(ARGS) --output-on-failure)
add_dependencies(check_python_parallel_odd pypresso python_test_data)
add_custom_target(check_python_gpu COMMAND ${CMAKE_CTEST_COMMAND} --timeout ${TEST_TIMEOUT} -j${TEST_NP} -L gpu $(ARGS) --output-on-failure)
add_dependencies(check_python_gpu pypresso python_test_data)

add_custom_target(check_python_skip_long COMMAND ${CMAKE_CTEST_COMMAND} --timeout ${TEST_TIMEOUT} -j${TEST_NP} -LE long $(ARGS) --output-on-failure)
add_dependencies(check_python_skip_long pypresso python_test_data)

add_custom_target(check_python COMMAND ${CMAKE_CTEST_COMMAND} --timeout ${TEST_TIMEOUT} -j${TEST_NP} $(ARGS) --output-on-failure)
add_dependencies(check_python pypresso python_test_data)
add_dependencies(check check_python)


