{"id":18840154,"url":"https://github.com/fortran-lang/test-drive","last_synced_at":"2025-08-25T22:11:15.655Z","repository":{"id":37033376,"uuid":"347741022","full_name":"fortran-lang/test-drive","owner":"fortran-lang","description":"The simple testing framework","archived":false,"fork":false,"pushed_at":"2024-09-15T09:55:15.000Z","size":107,"stargazers_count":102,"open_issues_count":11,"forks_count":31,"subscribers_count":17,"default_branch":"main","last_synced_at":"2025-08-13T05:42:07.140Z","etag":null,"topics":["fortran-library","testing-framework","unit-testing"],"latest_commit_sha":null,"homepage":"","language":"Fortran","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fortran-lang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-Apache","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-14T20:01:54.000Z","updated_at":"2025-08-04T15:44:04.000Z","dependencies_parsed_at":"2023-02-18T23:15:42.370Z","dependency_job_id":"41cfc2c5-8cd7-4d37-af6f-2133f8d7b172","html_url":"https://github.com/fortran-lang/test-drive","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/fortran-lang/test-drive","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fortran-lang%2Ftest-drive","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fortran-lang%2Ftest-drive/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fortran-lang%2Ftest-drive/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fortran-lang%2Ftest-drive/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fortran-lang","download_url":"https://codeload.github.com/fortran-lang/test-drive/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fortran-lang%2Ftest-drive/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272139629,"owners_count":24880336,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-25T02:00:12.092Z","response_time":1107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fortran-library","testing-framework","unit-testing"],"created_at":"2024-11-08T02:45:13.559Z","updated_at":"2025-08-25T22:11:15.600Z","avatar_url":"https://github.com/fortran-lang.png","language":"Fortran","funding_links":[],"categories":[],"sub_categories":[],"readme":"# The simple testing framework\n\n[![License](https://img.shields.io/badge/license-MIT%7CApache%202.0-blue)](LICENSE-Apache)\n[![Latest Version](https://img.shields.io/github/v/release/fortran-lang/test-drive)](https://github.com/fortran-lang/test-drive/releases/latest)\n[![CI](https://github.com/fortran-lang/test-drive/workflows/CI/badge.svg)](https://github.com/fortran-lang/test-drive/actions)\n[![codecov](https://codecov.io/gh/fortran-lang/test-drive/branch/main/graph/badge.svg)](https://codecov.io/gh/fortran-lang/test-drive)\n\nThis project offers a lightweight, procedural unit testing framework based on nothing but standard Fortran.\nIntegration with [meson](https://mesonbuild.com), [cmake](https://cmake.org) and [Fortran package manager (fpm)](https://github.com/fortran-lang/fpm) is available.\nAlternatively, the [``testdrive.F90``](src/testdrive.F90) source file can be redistributed in the project's testsuite as well.\n\n\n## Usage\n\nTestsuites are defined by a ``collect_interface`` returning a set of ``unittest_type`` objects.\nTo create a new test use the ``new_unittest`` constructor, which requires a test identifier and a procedure with a ``test_interface`` compatible signature.\nThe error status is communicated by the allocation status of an ``error_type``.\n\nThe necessary boilerplate code to setup the test entry point is just\n\n```fortran\nprogram tester\n  use, intrinsic :: iso_fortran_env, only : error_unit\n  use testdrive, only : run_testsuite, new_testsuite, testsuite_type\n  use test_suite1, only : collect_suite1\n  use test_suite2, only : collect_suite2\n  implicit none\n  integer :: stat, is\n  type(testsuite_type), allocatable :: testsuites(:)\n  character(len=*), parameter :: fmt = '(\"#\", *(1x, a))'\n\n  stat = 0\n\n  testsuites = [ \u0026\n    new_testsuite(\"suite1\", collect_suite1), \u0026\n    new_testsuite(\"suite2\", collect_suite2) \u0026\n    ]\n\n  do is = 1, size(testsuites)\n    write(error_unit, fmt) \"Testing:\", testsuites(is)%name\n    call run_testsuite(testsuites(is)%collect, error_unit, stat)\n  end do\n\n  if (stat \u003e 0) then\n    write(error_unit, '(i0, 1x, a)') stat, \"test(s) failed!\"\n    error stop\n  end if\n\nend program tester\n```\n\nEvery test is defined in a separate module using a ``collect`` function, which is exported and added to the ``testsuites`` array in the test runner.\nAll tests have a simple interface with just an allocatable ``error_type`` as output to provide the test results.\n\n```fortran\nmodule test_suite1\n  use testdrive, only : new_unittest, unittest_type, error_type, check\n  implicit none\n  private\n\n  public :: collect_suite1\n\ncontains\n\n!\u003e Collect all exported unit tests\nsubroutine collect_suite1(testsuite)\n  !\u003e Collection of tests\n  type(unittest_type), allocatable, intent(out) :: testsuite(:)\n\n  testsuite = [ \u0026\n    new_unittest(\"valid\", test_valid), \u0026\n    new_unittest(\"invalid\", test_invalid, should_fail=.true.) \u0026\n    ]\n\nend subroutine collect_suite1\n\nsubroutine test_valid(error)\n  type(error_type), allocatable, intent(out) :: error\n  ! ...\nend subroutine test_valid\n\nsubroutine test_invalid(error)\n  type(error_type), allocatable, intent(out) :: error\n  ! ...\nend subroutine test_invalid\n\nend module test_suite1\n```\n\n\n### Checking test conditions\n\nThe procedures defining the tests can contain any Fortran code required for checking the correctness of the project.\nAn easy way to do so is provided by the generic ``check`` function.\n\n```f90\nsubroutine test_valid(error)\n  type(error_type), allocatable, intent(out) :: error\n\n  call check(error, 1 + 2 == 3)\n  if (allocated(error)) return\n\n  ! equivalent to the above\n  call check(error, 1 + 2, 3)\n  if (allocated(error)) return\nend subroutine test_valid\n```\n\nAfter each check, the status of the error should be checked.\nUncaught errors will not be silently dropped, instead the error will be caught, its message displayed and the run aborted.\nPossible ways to use check are listed below\n\n| available checks     | arguments                                                      |\n| -------------------- | -------------------------------------------------------------- |\n| logical check        | *error*, *logical*, ...                                        |\n| status check         | *error*, *integer*, ...                                        |\n| logical comparison   | *error*, *logical*, *logical*, ...                             |\n| integer comparison   | *error*, *integer*, ...                                        |\n| character comparison | *error*, *character*, *character*, ...                         |\n| real comparison      | *error*, *real*, *real*, ..., thr=*real*, rel=*logical*        |\n| real NaN check       | *error*, *real*, ...                                           |\n| complex comparison   | *error*, *complex*, *complex*, ..., thr=*real*, rel=*logical*  |\n| complex NaN check    | *error*, *complex*, ...                                        |\n\nEach check will generate a meaningful error message based on the available arguments, but can also be provided with a custom error message instead.\n\nTo generate custom checks the ``test_failed`` procedure is available to generate error messages\n\n```f90\nsubroutine test_custom(error)\n  type(error_type), allocatable, intent(out) :: error\n\n  ! ...\n\n  if (.not.cond) then\n    call test_failed(error, \"Custom check failed\")\n    return\n  end if\n\n  ! ...\n\n  if (.not.cond) then\n    call test_failed(error, \"Custom check failed\", \"Additional context\")\n    return\n  end if\n\nend subroutine test_custom\n```\n\nTo conditionally skip a test use the ``skip_test`` procedure.\nIt uses the same signature as ``test_failed``, but will mark the respective test as skipped, this is useful to disable tests based on conditional compilation, *e.g.* by using a preprocessor or a different submodule.\nAn uncaught skipped test will fail regardless, therefore make sure to not run any other checks afterwards.\n\n\n### Integration in build systems\n\nFinally, for usage with *fpm* it is beneficial to have a single test driver which can run all tests.\nWhile this brings the disadvantage of always having to run the complete testsuite, the main driver can provide the flexibility to select the suite and also the unit test using the boilerplate code shown here:\n\n```f90\n!\u003e Driver for unit testing\nprogram tester\n  use, intrinsic :: iso_fortran_env, only : error_unit\n  use testdrive, only : run_testsuite, new_testsuite, testsuite_type, \u0026\n    \u0026 select_suite, run_selected, get_argument\n  use test_suite1, only : collect_suite1\n  use test_suite2, only : collect_suite2\n  implicit none\n  integer :: stat, is\n  character(len=:), allocatable :: suite_name, test_name\n  type(testsuite_type), allocatable :: testsuites(:)\n  character(len=*), parameter :: fmt = '(\"#\", *(1x, a))'\n\n  stat = 0\n\n  testsuites = [ \u0026\n    new_testsuite(\"suite1\", collect_suite1), \u0026\n    new_testsuite(\"suite2\", collect_suite2) \u0026\n    ]\n\n  call get_argument(1, suite_name)\n  call get_argument(2, test_name)\n\n  if (allocated(suite_name)) then\n    is = select_suite(testsuites, suite_name)\n    if (is \u003e 0 .and. is \u003c= size(testsuites)) then\n      if (allocated(test_name)) then\n        write(error_unit, fmt) \"Suite:\", testsuites(is)%name\n        call run_selected(testsuites(is)%collect, test_name, error_unit, stat)\n        if (stat \u003c 0) then\n          error stop 1\n        end if\n      else\n        write(error_unit, fmt) \"Testing:\", testsuites(is)%name\n        call run_testsuite(testsuites(is)%collect, error_unit, stat)\n      end if\n    else\n      write(error_unit, fmt) \"Available testsuites\"\n      do is = 1, size(testsuites)\n        write(error_unit, fmt) \"-\", testsuites(is)%name\n      end do\n      error stop 1\n    end if\n  else\n    do is = 1, size(testsuites)\n      write(error_unit, fmt) \"Testing:\", testsuites(is)%name\n      call run_testsuite(testsuites(is)%collect, error_unit, stat)\n    end do\n  end if\n\n  if (stat \u003e 0) then\n    write(error_unit, '(i0, 1x, a)') stat, \"test(s) failed!\"\n    error stop 1\n  end if\n\nend program tester\n```\n\nFrom *fpm* this allows to run all tests using just the *fpm test* command, but also to debug an individual test in a debugger.\nFor example to run *broken-test* in *large-suite* with ``gdb`` use\n\n```\nfpm test --runner gdb -- large-suite broken-test\n```\n\nTo make this approach feasible for meson the tests can be created as individual suites.\nA usual layout of the test directory like\n\n```\ntest\n├── main.f90\n├── meson.build\n├── test_suite1.f90\n├── test_suite2.f90\n└── ...\n```\n\nCan use the following snippet to automatically create individual tests running complete suites inside the driver.\nResolution to the unit tests is possible but usually not desired, because the individual runtime of the tests will be short compared to the overhead to start the actual test.\n\n```meson\ntestdrive_dep = dependency('test-drive', fallback: ['test-drive', 'testdrive_dep'])\n\ntests = [\n  'suite1',\n  'suite2',\n  # ...\n]\n\ntest_srcs = files(\n  'main.f90',\n)\nforeach t : tests\n  test_srcs += files('test_@0@.f90'.format(t.underscorify()))\nendforeach\n\ntester = executable(\n  'tester',\n  sources: test_srcs,\n  dependencies: [proj_dep, testdrive_dep],\n)\n\ntest('all tests', tester)\n\nforeach t : tests\n  test(t, tester, args: t)\nendforeach\n```\n\nSimilar for a CMake based build the tests can be generated automatically for the layout shown below.\n\n```\ntest\n├── CMakeLists.txt\n├── main.f90\n├── test_suite1.f90\n├── test_suite2.f90\n└── ...\n```\n\nThe CMake file in the test directory should look similar to the one shown here\n\n```cmake\nif(NOT TARGET \"test-drive::test-drive\")\n  find_package(\"test-drive\" REQUIRED)\nendif()\n\n# Unit testing\nset(\n  tests\n  \"suite1\"\n  \"suite2\"\n)\nset(\n  test-srcs\n  \"main.f90\"\n)\nforeach(t IN LISTS tests)\n  string(MAKE_C_IDENTIFIER ${t} t) \n  list(APPEND test-srcs \"test_${t}.f90\")\nendforeach()\n\nadd_executable(\n  \"${PROJECT_NAME}-tester\"\n  \"${test-srcs}\"\n)\ntarget_link_libraries(\n  \"${PROJECT_NAME}-tester\"\n  PRIVATE\n  \"${PROJECT_NAME}-lib\"\n  \"test-drive::test-drive\"\n)\n\nforeach(t IN LISTS tests)\n  add_test(\"${PROJECT_NAME}/${t}\" \"${PROJECT_NAME}-tester\" \"${t}\")\nendforeach()\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eCMake module to find testing framework\u003c/summary\u003e\n\nThe following module allows to find or fetch an installation of this project in CMake\n\n```cmake\n#[[.rst:\nFind test-drive\n---------------\n\nMakes the test-drive project available.\n\nImported Targets\n^^^^^^^^^^^^^^^^\n\nThis module provides the following imported target, if found:\n\n``test-drive::test-drive``\n  The test-drive library\n\n\nResult Variables\n^^^^^^^^^^^^^^^^\n\nThis module will define the following variables:\n\n``TEST_DRIVE_FOUND``\n  True if the test-drive library is available\n\n``TEST_DRIVE_SOURCE_DIR``\n  Path to the source directory of the test-drive project,\n  only set if the project is included as source.\n\n``TEST_DRIVE_BINARY_DIR``\n  Path to the binary directory of the test-drive project,\n  only set if the project is included as source.\n\nCache variables\n^^^^^^^^^^^^^^^\n\nThe following cache variables may be set to influence the library detection:\n\n``TEST_DRIVE_FIND_METHOD``\n  Methods to find or make the project available. Available methods are\n  - ``cmake``: Try to find via CMake config file\n  - ``pkgconf``: Try to find via pkg-config file\n  - ``subproject``: Use source in subprojects directory\n  - ``fetch``: Fetch the source from upstream\n\n``TEST_DRIVE_DIR``\n  Used for searching the CMake config file\n\n``TEST_DRIVE_SUBPROJECT``\n  Directory to find the test-drive subproject, relative to the project root\n\n``TEST_DRIVE_GIT_TAG``\n  The tag to use if fetching from git.\n\n#]]\n\nset(_lib \"test-drive\")\nset(_pkg \"TEST_DRIVE\")\nset(_url \"https://github.com/fortran-lang/test-drive\")\n\nif(NOT DEFINED \"${_pkg}_FIND_METHOD\")\n  if(DEFINED \"${PROJECT_NAME}-dependency-method\")\n    set(\"${_pkg}_FIND_METHOD\" \"${${PROJECT_NAME}-dependency-method}\")\n  else()\n    set(\"${_pkg}_FIND_METHOD\" \"cmake\" \"pkgconf\" \"subproject\" \"fetch\")\n  endif()\n  set(\"_${_pkg}_FIND_METHOD\")\nendif()\n\nforeach(method ${${_pkg}_FIND_METHOD})\n  if(TARGET \"${_lib}::${_lib}\")\n    break()\n  endif()\n\n  if(\"${method}\" STREQUAL \"cmake\")\n    message(STATUS \"${_lib}: Find installed package\")\n    if(DEFINED \"${_pkg}_DIR\")\n      set(\"_${_pkg}_DIR\")\n      set(\"${_lib}_DIR\" \"${_pkg}_DIR\")\n    endif()\n    find_package(\"${_lib}\" CONFIG)\n    if(\"${_lib}_FOUND\")\n      message(STATUS \"${_lib}: Found installed package\")\n      break()\n    endif()\n  endif()\n\n  if(\"${method}\" STREQUAL \"pkgconf\")\n    find_package(PkgConfig QUIET)\n    pkg_check_modules(\"${_pkg}\" QUIET \"${_lib}\")\n    if(\"${_pkg}_FOUND\")\n      message(STATUS \"Found ${_lib} via pkg-config\")\n\n      add_library(\"${_lib}::${_lib}\" INTERFACE IMPORTED)\n      target_link_libraries(\n        \"${_lib}::${_lib}\"\n        INTERFACE\n        \"${${_pkg}_LINK_LIBRARIES}\"\n        )\n      target_include_directories(\n        \"${_lib}::${_lib}\"\n        INTERFACE\n        \"${${_pkg}_INCLUDE_DIRS}\"\n        )\n\n      break()\n    endif()\n  endif()\n\n  if(\"${method}\" STREQUAL \"subproject\")\n    if(NOT DEFINED \"${_pkg}_SUBPROJECT\")\n      set(\"_${_pkg}_SUBPROJECT\")\n      set(\"${_pkg}_SUBPROJECT\" \"subprojects/${_lib}\")\n    endif()\n    set(\"${_pkg}_SOURCE_DIR\" \"${PROJECT_SOURCE_DIR}/${${_pkg}_SUBPROJECT}\")\n    set(\"${_pkg}_BINARY_DIR\" \"${PROJECT_BINARY_DIR}/${${_pkg}_SUBPROJECT}\")\n    if(EXISTS \"${${_pkg}_SOURCE_DIR}/CMakeLists.txt\")\n      message(STATUS \"Include ${_lib} from ${${_pkg}_SUBPROJECT}\")\n      add_subdirectory(\n        \"${${_pkg}_SOURCE_DIR}\"\n        \"${${_pkg}_BINARY_DIR}\"\n      )\n\n      add_library(\"${_lib}::${_lib}\" INTERFACE IMPORTED)\n      target_link_libraries(\"${_lib}::${_lib}\" INTERFACE \"${_lib}\")\n\n      # We need the module directory in the subproject before we finish the configure stage\n      if(NOT EXISTS \"${${_pkg}_BINARY_DIR}/include\")\n        make_directory(\"${${_pkg}_BINARY_DIR}/include\")\n      endif()\n\n      break()\n    endif()\n  endif()\n\n  if(\"${method}\" STREQUAL \"fetch\")\n    if(NOT DEFINED \"${_pkg}_GIT_TAG\")\n      set(\"_${_pkg}_GIT_TAG\")\n      set(\"${_pkg}_GIT_TAG\" \"HEAD\")\n    endif()\n    message(STATUS \"Retrieving ${_lib} from ${_url} with tag ${${_pkg}_GIT_TAG}\")\n    include(FetchContent)\n    FetchContent_Declare(\n      \"${_lib}\"\n      GIT_REPOSITORY \"${_url}\"\n      GIT_TAG \"${${_pkg}_GIT_TAG}\"\n      )\n    FetchContent_MakeAvailable(\"${_lib}\")\n\n    add_library(\"${_lib}::${_lib}\" INTERFACE IMPORTED)\n    target_link_libraries(\"${_lib}::${_lib}\" INTERFACE \"${_lib}\")\n\n    # We need the module directory in the subproject before we finish the configure stage\n    FetchContent_GetProperties(\"${_lib}\" SOURCE_DIR \"${_pkg}_SOURCE_DIR\")\n    FetchContent_GetProperties(\"${_lib}\" BINARY_DIR \"${_pkg}_BINARY_DIR\")\n    if(NOT EXISTS \"${${_pkg}_BINARY_DIR}/include\")\n      make_directory(\"${${_pkg}_BINARY_DIR}/include\")\n    endif()\n\n    break()\n  endif()\n\nendforeach()\n\nif(TARGET \"${_lib}::${_lib}\")\n  set(\"${_pkg}_FOUND\" TRUE)\nelse()\n  set(\"${_pkg}_FOUND\" FALSE)\nendif()\n\nif(DEFINED \"_${_pkg}_GIT_TAG\")\n  unset(\"${_pkg}_GIT_TAG\")\n  unset(\"_${_pkg}_GIT_TAG\")\nendif()\nif(DEFINED \"_${_pkg}_SUBPROJECT\")\n  unset(\"${_pkg}_SUBPROJECT\")\n  unset(\"_${_pkg}_SUBPROJECT\")\nendif()\nif(DEFINED \"_${_pkg}_DIR\")\n  unset(\"${_lib}_DIR\")\n  unset(\"_${_pkg}_DIR\")\nendif()\nif(DEFINED \"_${_pkg}_FIND_METHOD\")\n  unset(\"${_pkg}_FIND_METHOD\")\n  unset(\"_${_pkg}_FIND_METHOD\")\nendif()\nunset(_lib)\nunset(_pkg)\nunset(_url)\n```\n\u003c/details\u003e\n\n\n## License\n\nThis project is free software: you can redistribute it and/or modify it under the terms of the [Apache License, Version 2.0](LICENSE-Apache) or [MIT license](LICENSE-MIT) at your opinion.\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an _as is_ basis, without warranties or conditions of any kind, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n\nUnless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffortran-lang%2Ftest-drive","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffortran-lang%2Ftest-drive","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffortran-lang%2Ftest-drive/lists"}