{"id":18847661,"url":"https://github.com/everythingfunctional/vegetables","last_synced_at":"2026-02-01T09:30:16.399Z","repository":{"id":146074008,"uuid":"476480228","full_name":"everythingfunctional/vegetables","owner":"everythingfunctional","description":"For a healthier code base, eat your vegetables","archived":false,"fork":false,"pushed_at":"2022-04-15T16:20:22.000Z","size":742,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-12-30T13:55:16.427Z","etag":null,"topics":["fortran","testing","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":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/everythingfunctional.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-03-31T21:13:11.000Z","updated_at":"2022-04-06T20:53:53.000Z","dependencies_parsed_at":"2023-05-11T17:00:24.804Z","dependency_job_id":null,"html_url":"https://github.com/everythingfunctional/vegetables","commit_stats":{"total_commits":458,"total_committers":6,"mean_commits":76.33333333333333,"dds":0.3013100436681223,"last_synced_commit":"5625f1f3e318fb301d654e7875e254fa3e0cc4a1"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/everythingfunctional%2Fvegetables","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/everythingfunctional%2Fvegetables/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/everythingfunctional%2Fvegetables/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/everythingfunctional%2Fvegetables/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/everythingfunctional","download_url":"https://codeload.github.com/everythingfunctional/vegetables/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239783468,"owners_count":19696403,"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","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","testing","unit-testing"],"created_at":"2024-11-08T03:09:07.990Z","updated_at":"2026-02-01T09:30:16.361Z","avatar_url":"https://github.com/everythingfunctional.png","language":"Fortran","funding_links":[],"categories":[],"sub_categories":[],"readme":"Vegetables\n==========\n\n[![pipeline status](https://gitlab.com/everythingfunctional/vegetables/badges/main/pipeline.svg)](https://gitlab.com/everythingfunctional/vegetables/-/commits/main)\n\n## This Project is Now Dormant!\n\nIt has been superseded by the new projects\n[veggies](https://gitlab.com/everythingfunctional/veggies) and\n[garden](https://gitlab.com/everythingfunctional/garden).\n\nFor a healthier code base, eat your vegetables.\n\nVegetables is a Fortran unit testing framework written using functional programming principles.\nAs many of its procedures as possible are marked with the pure keyword,\nwhile still allowing the framework to test impure code.\nIt's biggest features are that it provides a readable test/code specification as it's output by default,\nand provides the option to output even the passing test results.\nIt makes writing tests in a Specification or [BDD] style as easy as possible,\nand has all the features one would expect from a modern testing framework.\nIt is also flexible and extensible.\n\n[BDD]: https://en.wikipedia.org/wiki/Behavior-driven_development\n\n## Environment\n\nIn order to run this you will need:\n* A reasonably recent version of gfortran, ifort or nagfor\n* The Fortran Package Manager (fpm)\n\nRunning the tests is then as simple as\n\n```\ngit clone https://gitlab.com/everythingfunctional/vegetables.git\ncd vegetables\nfpm test\n```\n\nUsing Vegetables\n----------------\n\nUsing Vegetables is (almost) as easy as writing a specification.\nA great first example are the tests for Vegetables themselves.\nThe following overview should be sufficient to get you started,\nbut you can take a deeper look into how vegetables works by referencing\n[the developer documentation](https://everythingfunctional.gitlab.io/vegetables).\n\n### Writing a Test Function\n\nThe simplest test function is one that takes no arguments and produces a `result_t` value as its output.\nThe function should execute some portion of your code, and make some assertion(s) about the result(s).\nMultiple assertions can be combined by using the `.and.` operator.\nAlso, the `succeed` and `fail` functions are provided if for some reason the provided assertions aren't quite sufficient.\n\nThe simplest example of a test function would be something like the following:\n\n```Fortran\nfunction simplest()\n    use vegetables, only: result_t, succeed\n\n    type(result_t) :: simplest\n\n    simplest = succeed(\"Successfully\")\nend function\n```\n\nAdditionally, a test function can take a `class(input_t)` value as an input.\nSome simple types are provided that extend from `input_t`,\nsuch as `double_precision_input_t`, `integer_input_t` and `string_input_t`.\nIf you need other inputs to your test, you can create a new type extended from `input_t` to provide whatever you need.\nA `select case` construct is then needed to make sure the right type gets passed in at run time.\nAn example of a test that takes an input is shown below.\nThe actual inputs passed to the test are determined by how it is included into the test suite.\nAssembling the test suite is described below.\n\n```Fortran\nfunction input_test(input) result(result_)\n    use vegetables, only: input_t, integer_input_t, result_t, assert_equals, fail\n\n    class(input_t), intent(in) :: input\n    type(result_t) :: result_\n\n    select type (input)\n    type is (integer_input_t)\n        result_ = assert_equals(1, input%value_)\n    class default\n        result_ = fail(\"Didn't get an integer_input_t\")\n    end select\nend function\n```\n\n### Assertions\n\nAn assertion is simply something to make sure that your code is behaving as expected.\nThere are multiple assert functions provided to check a variety of types of values.\nThey all return a value of type `result_t` that can be returned by your test function.\nAdditionally, `result_t` values can be combined using the `.and.` operator\nto allow you to make multiple assertions within your test and return the results of all of them.\nAdditionally, the `succeed` and `fail` functions are provided for situations where the provided assertions aren't quite appropriate.\n\nBy convention, there are two final arguments to the assert functions which are optional that represent an additional *user* message to be included.\nIf both arguments are included then the first is used in the case the assertion succeeds,\nand the second is used in the case the assertion fails.\nIf only one is provided, then it is used whether the assertion passes or fails.\nAny argument of type `character` can also accept a value of type `varying_string`\nfrom the `iso_varying_string` module.\nThe provided assertions are listed below:\n\n* `assert_that` and `assert_not` accept a logical value and make sure it is either `.true.` or `.false.` respectively\n* `assert_equals` accepts two values of integer, double precision, character, or `varying_string`, and ensures they are equal\n  (Note that `assert_equals` with double precision values simply uses the `assert_equals_within_absolute` function with a tolerance of machine epsilon)\n* `assert_equals_within_absolute` and `assert_equals_within_relative` accept two values of double precision\n  and a third value of double precision to use as the tolerance, and ensures the values are within that tolerance.\n* `assert_empty` ensures that the given string is of zero length\n* `assert_includes` and `assert_doesnt_include` ensure that the second string includes (or doesn't include) the first string\n* `assert_faster_than` has several variations.\n  It accepts a subroutine with no arguments, and runs it the specified number of times to measure how long it takes to run.\n  It then compares that to either a given number in seconds, or from running another provided subroutine and measuring it as well.\n  Optionally, additional subroutines can be provided to be executed before and after the other subroutine(s) to function as setup and tear-down,\n  to avoid including that code in the measurement.\n\n#### Writing Your Own Assertions\n\nAll of the code used to create the messages from the assertions is publicly callable.\nThe basic pattern used is:\n\n```Fortran\nif (some_condition) then\n    result_ = succeed( \u0026\n            with_user_message( \u0026\n                    make_some_success_message(args), \u0026\n                    success_message))\nelse\n    result_ = fail( \u0026\n            with_user_message( \u0026\n                    make_some_failure_message(args), \u0026\n                    failure_message))\nend if\n```\n\nSo if the provided assert functions don't quite do what you need, or don't accept the type you need,\nit should be pretty easy to write your own,\nand have the style of the message it produces match with the rest of them.\nThe [Quantities for Fortran](https://gitlab.com/everythingfunctional/quaff) is a great example of where I've done just that.\n\n### Assemble The Suite\n\nOnce you've written your test function, you'll need to include it into your test suite.\nI've [published a tool](https://gitlab.com/everythingfunctional/make_vegetable_driver)\nthat can be used to do it, but you can also do it manually.\n\nFirst, you'll need to write a function that defines a part of your test suite,\neither spec or BDD style, using the provided functions `describe` and `it`/`it_`\nor `given`, `when` and `then_`/`then__`.\nIf you're using the provided tool, then this function should be in a module named `something`**`_test`**,\nin a file with the same name.\nThe function should be named **`test_`**`something`,\nand must take no arguments, and return a value of type `test_item_t`, which the above functions do.\n\nThe `given`, `when` and `describe` functions take a description string, and an array of `test_item_t`s.\nThe `it` and `then_` functions accept a string description,\nand a function that takes no arguments and returns a `result_t`.\nThe `it_` and `then__` functions accept a function that takes one argument of `class(input_t)`.\nThese are the descriptions that are given in the output of Vegetables for each test.\n\nThe `given`, `when` and `describe` functions can also accept a `class(input_t)` value,\nto be passed to each of the tests they contain.\nAdditionally, the `when` function can accept a function from `class(input_t)` to `type(transformed_t)`\n(which is just a wrapper for a `class(input_t)` value)\nwhich will be called and then pass its result down to the tests.\n\nThe `it` and `then_` functions can also take an array of `example_t`s\n(which is just a wrapper for a `class(input_t)`)\nwhich will each be passed to the test function.\nThey can instead accept a value of `class(generator_t)`,\nwhich will be used to generate random values to be passed to the test.\nMore information is provided for `class(generator_t)` below.\n\nAn example of a specification function would be as follows:\n\n```Fortran\nfunction test_assert_empty() result(tests)\n    type(test_item_t) :: tests\n\n    tests = describe( \u0026\n            \"assert_empty\", \u0026\n            [ it( \u0026\n                    \"passes with an empty string\", \u0026\n                    check_pass_for_empty_chars) \u0026\n            , it( \u0026\n                    \"fails with a non empty string\", \u0026\n                    check_fails_for_nonempty_chars) \u0026\n            ])\nend function\n```\n\nThe basic gist of this is that we are describing the requirements for the `assert_empty` function.\nThe two requirements are that:\n\n1. It passes with an empty string\n2. It fails with a non-empty string\n\nIf you are using the provided tool,\nmultiple **`test_`**`something` functions can be provided within a module,\nand multiple `something`**`_test`** modules can be provided in separate files.\nThe tool will generate a driver program that calls each **`test_`**`something` function it finds\nin order to build up the test suite.\nIt will then run all of the tests and report the results.\nThe tool accepts as command line arguments the name of the generator program\nand the list of files containing the tests.\n\nThe generated driver program uses the function `test_that`\nto combine all of the tests provided by the **`test_`**`something` functions into a single collection,\nand then calls the subroutine `run_tests` with that collection.\nSo, even manually writing and maintaining the driver program wouldn't be _too_ bad.\n\n### Generating Random Inputs\n\nBy providing a type extended from `class(generator_t)`,\nyou can test fundamental properties of your code that should hold for all values.\nA couple of generators are provided for simple types,\nbut generally you'll want to provide your own.\nTo do so you'll need to override the `generate` function.\nThis function takes your generator type as an input, and must produce a value of `type(generated_t)`.\nThis is just a wrapper around a `class(input_t)`.\nSeveral `get_random*` functions are provided to generate most primitive types with various ranges and/or lengths.\n\nAdditionally, you must override the `shrink` function.\nThis must take a `class(input_t)` value as input, and provide a `type(shrink_result_t)` as output.\nThis is just a wrapper around a `class(input_t)`, with a flag for whether it is the simplest possible value.\nThe relevant code for one of the provided generators is shown below.\n\n```Fortran\nmodule vegetables_integer_generator_m\n    use vegetables_generator_m, only: generator_t\n\n    implicit none\n    private\n    public :: INTEGER_GENERATOR\n\n    type, extends(generator_t) :: integer_generator_t\n    contains\n        private\n        procedure, public :: generate\n        procedure, nopass, public :: shrink\n    end type\n\n    type(integer_generator_t), parameter :: \u0026\n            INTEGER_GENERATOR = integer_generator_t()\ncontains\n    function generate(self) result(generated_value)\n        use vegetables_generated_m, only: generated_t\n        use vegetables_integer_input_m, only: integer_input_t\n        use vegetables_random_m, only: get_random_integer\n\n        class(integer_generator_t), intent(in) :: self\n        type(generated_t) :: generated_value\n\n        associate(unused =\u003e self)\n        end associate\n\n        generated_value = generated_t(integer_input_t(get_random_integer()))\n    end function\n\n    function shrink(input) result(shrunk)\n        use vegetables_input_m, only: input_t\n        use vegetables_integer_input_m, only: integer_input_t\n        use vegetables_shrink_result_m, only: \u0026\n                shrink_result_t, shrunk_value, simplest_value\n\n        class(input_t), intent(in) :: input\n        type(shrink_result_t) :: shrunk\n\n        select type (input)\n        type is (integer_input_t)\n            associate(input_val =\u003e input%input())\n                if (input_val == 0) then\n                    shrunk = simplest_value(integer_input_t(0))\n                else\n                    shrunk = shrunk_value(integer_input_t(input_val / 2))\n                end if\n            end associate\n        end select\n    end function\nend module\n```\n\nWhen given a generator, the `generate` function will be called to generate up to\nthe number random values given on the command line (the default is 100).\nIf the test never fails, it passes.\nIf the test fails on one of the inputs, then it will successively call the `shrink` function with that input\nuntil either the test passes again, or it gets to the simplest possible input.\nThe last failing result is then reported.\n\n### The Command Line\n\nThe driver program accepts a handful of command line arguments for controlling how the tests are run.\nThis is done inside the `run_tests` subroutine, so even manually or otherwise generated driver programs can use this functionality.\n\n```\nUsage: driver_name [-h] [-q] [-v] [-d] [-f string] [-n num] [-s num] [-c]\n  options:\n    -h, --help                    Output this message and exit\n    -q, --quiet                   Don't print the test descriptions before\n                                  running the tests\n    -v, --verbose                 Print all of the assertion messages, not\n                                  just the failing ones\n    -d, --debug                   Report the beginning and end of execution\n                                  of each test case or suite\n    -f string, --filter string    Only run cases or collections whose\n                                  description contains the given string\n    -n num, --numrand num         Number of random values to use for each\n                                  test with generated values (default = 100)\n    -s num, --shrink-max num      Number of attempts to find a simpler value\n                                  if a random value fails (default = 100)\n    -c, --color-off               Don't colorize the output\n```\n\nA common usage is to use the `-q`, `-v` and `-f` flags to run the tests you're working on\nand make sure they're doing what you expect.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feverythingfunctional%2Fvegetables","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feverythingfunctional%2Fvegetables","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feverythingfunctional%2Fvegetables/lists"}