├── .github ├── FUNDING.yml ├── issue_template.md └── workflows │ └── test.yml ├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── binaries.esy.json ├── bisect_ppx.opam ├── bsconfig.json ├── doc ├── CHANGES ├── advanced.md ├── convert-readme.js ├── footer.html ├── header.html ├── logo-initial.jpg ├── logo.png ├── logo0.png └── sample.gif ├── dune-project ├── package.json ├── src ├── common │ ├── bisect_common.ml │ ├── bisect_common.mli │ └── dune ├── npm-install.sh ├── ppx │ ├── dune │ ├── exclude.ml │ ├── exclude.mli │ ├── exclude_lexer.mll │ ├── exclude_parser.mly │ ├── exclusions.ml │ ├── exclusions.mli │ ├── instrument.ml │ ├── instrument.mli │ ├── js │ │ ├── dune │ │ └── ppx.ml │ └── register.ml ├── report │ ├── cobertura.ml │ ├── cobertura.mli │ ├── coverage.css │ ├── coverage.js │ ├── coveralls.ml │ ├── coveralls.mli │ ├── dummy-binary │ ├── dune │ ├── html.ml │ ├── html.mli │ ├── input.ml │ ├── input.mli │ ├── main.ml │ ├── merge.ml │ ├── merge.mli │ ├── text.ml │ ├── text.mli │ ├── util.ml │ └── util.mli ├── runtime │ ├── js │ │ ├── jest.ml │ │ ├── runtime.ml │ │ └── runtime.mli │ └── native │ │ ├── dune │ │ ├── runtime.ml │ │ └── runtime.mli └── vendor │ └── highlight.js │ ├── CHANGES.md │ ├── LICENSE │ ├── README.md │ ├── README.ru.md │ └── highlight.pack.js └── test ├── .gitattributes ├── ci ├── binaries.sh └── travis-wrapped-esy.sh ├── dune ├── instrument ├── apply │ ├── and.t │ ├── apply.t │ ├── assert.t │ ├── dune │ ├── operator.t │ ├── or.t │ ├── pipe.t │ └── special.t ├── attribute.t ├── class │ ├── class.t │ ├── dune │ ├── instvar.t │ ├── method.t │ ├── new.t │ └── send.t ├── control │ ├── dune │ ├── for.t │ ├── fun.t │ ├── function.t │ ├── if.t │ ├── lazy.t │ ├── match.t │ ├── newtype.t │ ├── try.t │ └── while.t ├── dune ├── mangle.t ├── mli.t ├── pattern │ ├── binding.t │ ├── dune │ ├── exception.t │ ├── nary.t │ ├── nullary.t │ ├── row.t │ ├── unary.t │ └── when.t ├── recent │ ├── dune │ ├── error.t │ ├── exception-pattern.t │ ├── exclusions.t │ ├── gadt.t │ ├── let-exception.t │ ├── letop.t │ ├── opaque_identity.t │ ├── pattern-open.t │ ├── react.t │ └── refutation.t ├── shadow.t ├── structure.t ├── test.sh └── value.t ├── js ├── Makefile ├── bsconfig.json ├── expected ├── hello.re ├── hello.rei └── package.json ├── random.t ├── report ├── cobertura.t ├── directory.t ├── dune ├── empty.ml ├── html-tree │ ├── baz │ │ └── baz.ml │ ├── dune │ ├── foo │ │ ├── bar │ │ │ ├── bar_a.ml │ │ │ └── bar_b.ml │ │ └── foo.ml │ ├── html-tree.t │ └── test_tree.ml ├── html.t ├── line.t ├── merge.ml ├── merge.t ├── missing.t ├── send-to.t ├── test.ml ├── test_merge1.ml ├── test_merge2.ml ├── text.t └── truncate.t ├── self ├── bisect_ppx.diff └── meta_bisect_ppx.diff ├── sigterm ├── at_exit_hook.ml ├── at_exit_main.ml ├── daemon.ml ├── dune ├── sigterm.t └── sigterm_atexit.t └── usage ├── dune-conditional ├── Makefile ├── dune ├── dune-project ├── dune-workspace └── source.ml ├── dune-integration ├── Makefile ├── dune ├── dune-project ├── dune-workspace └── source.ml ├── dune-linkall ├── Makefile ├── dune ├── dune-project ├── dune-workspace └── source.ml ├── dune-unconditional ├── Makefile ├── dune ├── dune-project ├── dune-workspace └── source.ml ├── js_of_ocaml ├── Makefile ├── dune ├── dune-project ├── dune-workspace └── source.ml ├── js_of_ocaml_with_runtime_library ├── Makefile ├── dune ├── dune-project ├── dune-workspace └── source.ml ├── ocamlfind ├── Makefile └── source.ml └── reason ├── Makefile ├── dune ├── dune-project ├── dune-workspace └── source.re /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: aantron 2 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | rescript: 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | os: 10 | - ubuntu-20.04 11 | - macos-11 12 | # Build binaries on older systems to require lower versions of libc and 13 | # other system libraries. 14 | 15 | runs-on: ${{matrix.os}} 16 | steps: 17 | - uses: actions/checkout@v2 18 | - run: npm install esy 19 | - run: echo PATH=$(pwd)/node_modules/.bin:$PATH >> $GITHUB_ENV 20 | - run: make -C test/js full-test 21 | - if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/master' }} 22 | env: 23 | DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} 24 | run: bash ./test/ci/binaries.sh 25 | 26 | opam: 27 | if: ${{github.ref != 'refs/heads/binaries'}} 28 | 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | os: 33 | - ubuntu-latest 34 | ocaml: 35 | - 5.0.0 36 | - 4.14.1 37 | - 4.13.1 38 | - 4.12.1 39 | - 4.11.2 40 | - 4.10.2 41 | - 4.09.1 42 | - 4.08.1 43 | - 4.07.1 44 | - 4.06.1 45 | - 4.05.0 46 | - 4.04.2 47 | include: 48 | - os: macos-latest 49 | ocaml: 4.12.1 50 | # ocamlformat 0.16.0, used in testing, doesn't support OCaml 4.13, and 51 | # we are using a Linux binary of it. So, hold the Mac build back to 52 | # 4.12 for now, and install ocamlformat from opam on Mac. 53 | - os: windows-latest 54 | ocaml: 4.12.1 55 | 56 | runs-on: ${{matrix.os}} 57 | steps: 58 | - uses: actions/checkout@v2 59 | - uses: avsm/setup-ocaml@v2 60 | with: 61 | ocaml-compiler: ${{matrix.ocaml}} 62 | 63 | - name: Install opam dependencies 64 | run: opam install --deps-only --yes . 65 | 66 | - name: Install ocamlformat (Linux) 67 | if: ${{runner.os == 'Linux'}} 68 | run: | 69 | wget https://github.com/aantron/ocamlformat-binary/releases/download/0.15.0/ocamlformat 70 | sudo mv ocamlformat /usr/local/bin/ocamlformat 71 | sudo chmod a+x /usr/local/bin/ocamlformat 72 | - name: Install ocamlformat (non-Linux) 73 | if: ${{runner.os != 'Linux'}} 74 | run: opam install ocamlformat.0.16.0 --yes 75 | 76 | - name: Install coreutils (Mac) 77 | if: ${{runner.os == 'macOS'}} 78 | run: brew install coreutils 79 | 80 | - name: Version feedback 81 | run: | 82 | opam --version 83 | opam exec -- ocaml -version 84 | opam exec -- ocamlformat --version 85 | truncate --version 86 | 87 | - name: Build 88 | run: opam exec -- make build 89 | 90 | - name: Test 91 | if: ${{runner.os != 'Windows'}} 92 | run: | 93 | case `opam exec -- ocamlc -version` in 94 | "4.07.1") TEST_ALIAS=@compatible;; 95 | "4.06.1") TEST_ALIAS=@compatible;; 96 | "4.05.0") TEST_ALIAS=@compatible;; 97 | "4.04.2") TEST_ALIAS=@compatible;; 98 | *) TEST_ALIAS=@runtest;; 99 | esac 100 | unset GITHUB_ACTIONS 101 | unset GITHUB_RUN_NUMBER 102 | opam exec -- make test TEST=$TEST_ALIAS 103 | 104 | # Reason requires OCaml <= 4.12. 105 | - name: Usage test 106 | if: ${{matrix.ocaml == '4.12.1' && runner.os == 'Linux'}} 107 | run: | 108 | opam install reason js_of_ocaml --yes 109 | opam exec -- make clean-usage usage 110 | 111 | - name: Submit self-coverage to Coveralls 112 | if: ${{matrix.ocaml == '4.12.1' && runner.os == 'Linux'}} 113 | continue-on-error: true 114 | env: 115 | COVERALLS_REPO_TOKEN: ${{secrets.GITHUB_TOKEN}} 116 | PULL_REQUEST_NUMBER: ${{github.event.number}} 117 | run: | 118 | (unset GITHUB_ACTIONS && unset GITHUB_RUN_NUMBER && \ 119 | unset COVERALLS_REPO_TOKEN && unset PULL_REQUEST_NUMBER && \ 120 | opam exec -- make clean self-coverage) 121 | (cd _self && \ 122 | _build/install/default/bin/meta-bisect-ppx-report \ 123 | send-to Coveralls bisect*.meta) 124 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Working and output directories. 2 | _build/ 3 | gh-pages/ 4 | 5 | # Test working and coverage directories. 6 | _coverage/ 7 | _self/ 8 | 9 | # Scratch directory for notes, etc. 10 | scratch/ 11 | 12 | # Generated by Dune. 13 | *.install 14 | *.merlin 15 | 16 | # Editor files. 17 | *~ 18 | .vscode/ 19 | *.swp 20 | 21 | # Pollution from the usage tests. 22 | *.coverage 23 | a.out 24 | *.cmi 25 | *.cmx 26 | *.o 27 | 28 | # opam 2. 29 | _opam/ 30 | 31 | # ReScript, NPM, esy, Melange. 32 | node_modules/ 33 | package-lock.json 34 | lib/ 35 | _esy/ 36 | *.tgz 37 | package/ 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2008-2021 Xavier Clerc, Leonid Rozenberg, Anton Bachin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the “Software”), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : build 2 | build : 3 | dune build -p bisect_ppx 4 | 5 | .PHONY : watch 6 | watch : 7 | dune build -p bisect_ppx -w 8 | 9 | TEST := @runtest 10 | 11 | .PHONY : test 12 | test : build 13 | dune build -p bisect_ppx $(TEST) 14 | 15 | .PHONY : promote 16 | promote : 17 | dune promote 18 | 19 | SELF_COVERAGE := _self 20 | 21 | .PHONY : clean 22 | clean : 23 | dune clean 24 | make clean-usage 25 | make -C test/js clean 26 | rm -rf $(SELF_COVERAGE) 27 | 28 | INSTALLED_ENVIRONMENT := \ 29 | OCAMLPATH=`pwd`/_build/install/default/lib \ 30 | PATH=`pwd`/_build/install/default/bin:$$PATH 31 | 32 | .PHONY : usage 33 | usage : build 34 | for TEST in `ls -d test/usage/*` ; \ 35 | do \ 36 | echo ; \ 37 | echo ; \ 38 | $(INSTALLED_ENVIRONMENT) make -wC $$TEST || exit 2 ; \ 39 | done 40 | 41 | .PHONY : clean-usage 42 | clean-usage : 43 | for TEST in `ls -d test/usage/*` ; \ 44 | do \ 45 | make -wC $$TEST clean ; \ 46 | done 47 | 48 | GH_PAGES := gh-pages 49 | 50 | .PHONY : gh-pages 51 | gh-pages: 52 | cat doc/header.html > $(GH_PAGES)/index.html 53 | cat README.md | node doc/convert-readme.js >> $(GH_PAGES)/index.html 54 | cat doc/footer.html >> $(GH_PAGES)/index.html 55 | 56 | .PHONY : self-coverage 57 | self-coverage : self-coverage-workspace self-coverage-rename self-coverage-test 58 | 59 | SOURCES := bisect_ppx.opam dune-project src/ 60 | 61 | .PHONY : self-coverage-workspace 62 | self-coverage-workspace : 63 | rm -rf $(SELF_COVERAGE)/bisect_ppx 64 | rm -rf $(SELF_COVERAGE)/meta_bisect_ppx 65 | mkdir -p $(SELF_COVERAGE) 66 | touch $(SELF_COVERAGE)/dune-workspace 67 | mkdir -p $(SELF_COVERAGE)/meta_bisect_ppx 68 | mkdir -p $(SELF_COVERAGE)/bisect_ppx 69 | cp -r $(SOURCES) $(SELF_COVERAGE)/meta_bisect_ppx/ 70 | cp -r $(SOURCES) $(SELF_COVERAGE)/bisect_ppx/ 71 | mkdir -p $(SELF_COVERAGE)/bisect_ppx/test 72 | cp -r test $(SELF_COVERAGE)/bisect_ppx/test/ 73 | cd $(SELF_COVERAGE)/meta_bisect_ppx && \ 74 | patch --no-backup-if-mismatch -p2 < ../../test/self/meta_bisect_ppx.diff 75 | cd $(SELF_COVERAGE)/bisect_ppx && \ 76 | patch --no-backup-if-mismatch -p2 < ../../test/self/bisect_ppx.diff 77 | 78 | .PHONY : self-coverage-rename 79 | self-coverage-rename : 80 | mv \ 81 | $(SELF_COVERAGE)/meta_bisect_ppx/bisect_ppx.opam \ 82 | $(SELF_COVERAGE)/meta_bisect_ppx/meta_bisect_ppx.opam 83 | mv \ 84 | $(SELF_COVERAGE)/meta_bisect_ppx/src/common/bisect_common.ml \ 85 | $(SELF_COVERAGE)/meta_bisect_ppx/src/common/meta_bisect_common.ml 86 | mv \ 87 | $(SELF_COVERAGE)/meta_bisect_ppx/src/common/bisect_common.mli \ 88 | $(SELF_COVERAGE)/meta_bisect_ppx/src/common/meta_bisect_common.mli 89 | 90 | FILTER := 's/^\(\(---\|+++\) [^ \t]*\).*$$/\1/g' 91 | 92 | .PHONY : self-coverage-diff 93 | self-coverage-diff : 94 | find . -name .merlin | xargs rm -f 95 | diff -ru src _self/meta_bisect_ppx/src | \ 96 | sed $(FILTER) > \ 97 | test/self/meta_bisect_ppx.diff || \ 98 | true 99 | diff -ru src _self/bisect_ppx/src | \ 100 | sed $(FILTER) > \ 101 | test/self/bisect_ppx.diff || \ 102 | true 103 | 104 | EXPECTED_FILES := \ 105 | --expect bisect_ppx/src/ \ 106 | --do-not-expect bisect_ppx/src/ppx/js/ \ 107 | --do-not-expect bisect_ppx/src/runtime/js/ 108 | 109 | .PHONY : self-coverage-test 110 | self-coverage-test : 111 | cd $(SELF_COVERAGE) && rm -f bisect*.meta 112 | cd $(SELF_COVERAGE) && dune build @install --instrument-with meta_bisect_ppx 113 | cd $(SELF_COVERAGE) && \ 114 | dune build --force --instrument-with meta_bisect_ppx $(TEST) 115 | rm -rf _coverage 116 | cd $(SELF_COVERAGE) && \ 117 | _build/install/default/bin/meta-bisect-ppx-report \ 118 | html -o ../_coverage bisect*.meta $(EXPECTED_FILES) --verbose 119 | cd $(SELF_COVERAGE) && \ 120 | _build/install/default/bin/meta-bisect-ppx-report \ 121 | summary bisect*.meta --verbose 122 | @echo See _coverage/index.html 123 | -------------------------------------------------------------------------------- /binaries.esy.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "ocaml": ">= 4.2.0", 4 | "@opam/cmdliner": "^1.0.0", 5 | "@opam/dune": "^2.7.0", 6 | "@opam/ppxlib": ">= 0.28.0" 7 | }, 8 | "esy": { 9 | "build": "dune build -p bisect_ppx", 10 | "install": "esy-installer bisect_ppx.install", 11 | "buildsInSource": "_build" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /bisect_ppx.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | 3 | synopsis: "Code coverage for OCaml" 4 | license: "MIT" 5 | homepage: "https://github.com/aantron/bisect_ppx" 6 | doc: "https://github.com/aantron/bisect_ppx" 7 | bug-reports: "https://github.com/aantron/bisect_ppx/issues" 8 | 9 | dev-repo: "git+https://github.com/aantron/bisect_ppx.git" 10 | authors: [ 11 | "Xavier Clerc " 12 | "Leonid Rozenberg " 13 | "Anton Bachin " 14 | ] 15 | maintainer: [ 16 | "Anton Bachin " 17 | "Leonid Rozenberg " 18 | ] 19 | 20 | depends: [ 21 | "base-unix" 22 | "cmdliner" {>= "1.0.0"} 23 | "dune" {>= "2.7.0"} 24 | "ocaml" {>= "4.03.0"} 25 | "ppxlib" {>= "0.28.0"} 26 | 27 | "dune" {with-test & >= "3.0.0"} 28 | "ocamlformat" {with-test & = "0.16.0"} 29 | ] 30 | 31 | build: [ 32 | ["dune" "build" "-p" name "-j" jobs] 33 | ["dune" "build" "-p" name "-j" jobs "@compatible"] {with-test} 34 | ] 35 | 36 | description: "Bisect_ppx helps you test thoroughly. It is a small preprocessor 37 | that inserts instrumentation at places in your code, such as if-then-else and 38 | match expressions. After you run tests, Bisect_ppx gives a nice HTML report 39 | showing which places were visited and which were missed. 40 | 41 | Usage is simple - add package bisect_ppx when building tests, run your tests, 42 | then run the Bisect_ppx report tool on the generated visitation files." 43 | -------------------------------------------------------------------------------- /bsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bisect_ppx", 3 | "namespace": "Bisect", 4 | "sources": [ 5 | "src/common", 6 | "src/runtime/js" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /doc/advanced.md: -------------------------------------------------------------------------------- 1 | # Bisect_ppx advanced usage 2 | 3 |
4 | 5 | #### Table of contents 6 | 7 | - [Exhaustiveness checking](#Exhaustiveness) 8 | - [Excluding generated files from coverage](#Excluding) 9 | - [Environment variables](#EnvironmentVariables) 10 | - [Naming the output files](#OutFiles) 11 | - [Logging](#Logging) 12 | - [SIGTERM handling](#SIGTERM) 13 | - [Setting at compile time](#CompileTime) 14 | 15 | 16 | 17 |
18 | 19 | 20 | ## Exhaustiveness checking 21 | 22 | It is easy to accidentally fail to preprocess part of your project, by leaving 23 | out `(preprocess (pps bisect_ppx))` in one of its subdirectories. If this 24 | happens, and goes unnoticed, you may see misleading coverage statistics, because 25 | Bisect_ppx will simply not be aware of the missing files at all. It will not 26 | report them as either covered or not covered — they will just not be 27 | present in the report. 28 | 29 | To sanity-check this, you can pass the `--expect` option to `bisect-ppx-report`. 30 | For example, 31 | 32 | ``` 33 | bisect-ppx-report html --expect src/ 34 | ``` 35 | 36 | `bisect-ppx-report` will then recursively scan `src/` for any `.ml` and `.re` 37 | files, and check that all of them were included in the report. 38 | 39 | You may have a subdirectory of `src/` that should not be included. You can 40 | exclude it from the recursive scan with `--do-not-expect`: 41 | 42 | ``` 43 | bisect-ppx-report html --expect src/ --do-not-expect src/build_tool/ 44 | ``` 45 | 46 | You can also specify individual files with `--expect` and `--do-not-expect` by 47 | omitting the trailing path separator: 48 | 49 | ``` 50 | bisect-ppx-report html --expect src/ --do-not-expect src/build_tool.ml 51 | ``` 52 | 53 | 54 | 55 |
56 | 57 | 58 | ## Excluding generated files from coverage 59 | 60 | Whole files can be excluded by placing `[@@@coverage exclude_file]` anywhere in 61 | their top-level module. 62 | 63 | If you have generated code that you cannot easily place an attribute into, nor 64 | is it easy to avoid preprocessing it, you can pass the `--exclusions` option to 65 | the Bisect_ppx preprocessor: 66 | 67 | ``` 68 | (instrumentation 69 | (backend bisect_ppx --exclusions bisect.exclude) 70 | (deps bisect.exclude)) 71 | ``` 72 | 73 | This requires Dune 2.9 or later. 74 | 75 | Note that the paths to `bisect.exclude` might be different between the 76 | `preprocess` and `preprocessor_deps` stanzas, because `pps bisect_ppx` looks for 77 | the file relative to the root directory of your project, while 78 | `preprocessor_deps` looks in the same directory that the `dune` file is in. 79 | 80 | Here is what the `bisect.exclude` file can look like: 81 | 82 | ``` 83 | (* OCaml-style comments are okay. *) 84 | 85 | (* Exclude the file "foo.ml": *) 86 | file "foo.ml" 87 | 88 | (* Exclude all files whose names start with "test_": *) 89 | file regexp "test_.*" 90 | 91 | (* Exclude the top-level values "foo" and "bar" in "baz.ml": *) 92 | file "baz.ml" [ 93 | name "foo" 94 | name "bar" 95 | ] 96 | 97 | (* Exclude all top-level values whose names begin with "dbg_" in all 98 | files in "src/": *) 99 | file regexp "src/.*" [ regexp "dbg_.*" ] 100 | ``` 101 | 102 | All regular expressions are in the syntax of the [`Str`][Str] module. 103 | 104 | 105 | 106 |
107 | 108 | 109 | ## Environment variables 110 | 111 | A program instrumented by Bisect_ppx writes `.coverage` files, which contain the 112 | numbers of times various points in the program's code were visited during 113 | execution. Two environment variables are available to control the writing of 114 | these files. 115 | 116 | 117 | #### Naming the output files 118 | 119 | By default, the counts files are called `bisect0001.coverage`, 120 | `bisect0002.coverage`, etc. The prefix `bisect` can be changed by setting the 121 | environment variable `BISECT_FILE`. In particular, you can set it to something 122 | like `_coverage/bisect` to put the counts files in a different directory, in 123 | this example `_coverage/`. 124 | 125 | `BISECT_FILE` can also be used to control the prefix programmatically. For 126 | example, the following code bases the prefix on the program name, and puts the 127 | `.coverage` files into the system temporary directory: 128 | 129 | let () = 130 | let (//) = Filename.concat in 131 | let tmpdir = Filename.get_temp_dir_name () in 132 | Unix.putenv "BISECT_FILE" 133 | (tmpdir // Printf.sprintf "bisect-%s-" Sys.executable_name) 134 | 135 | 136 | #### Logging 137 | 138 | If the instrumented program fails to write an `.coverage` file, it will log a 139 | message. By default, these messages go to a file `bisect.log`. `BISECT_SILENT` 140 | can be set to `YES` to turn off logging completely. Alternatively, it can be set 141 | to another filename, or to `ERR` in order to log to `STDERR`. 142 | 143 | 144 | #### SIGTERM handling 145 | If `BISECT_SIGTERM` is set to `yes`, the Bisect runtime will install a SIGTERM 146 | handler that will write `.coverage` files and exit. This is useful if you have a 147 | large number of test programs, and they can be killed by another process. 148 | 149 | 150 | #### Setting at compile time 151 | 152 | If your testing environment doesn't allow you to easily specify these 153 | environment variables at testing time, you can specify default values for them 154 | at compile time by passing the `--bisect-file`, `--bisect-silent`, and/or 155 | `--bisect-sigterm` options to the Bisect_ppx instrumenter: 156 | 157 | ``` 158 | (instrumentation 159 | (backend bisect_ppx 160 | --bisect-file /tmp/mycoverage 161 | --bisect-silent /tmp/coverage.log 162 | --bisect-sigterm)) 163 | ``` 164 | 165 | If different values are specified in different `dune` files for code that is 166 | then linked into one binary, one set of values is chosen arbitrarily. 167 | 168 | Passing arguments to Bisect in this way requires Dune version 2.8.0 or higher. 169 | 170 | 171 | 172 | [Str]: http://caml.inria.fr/pub/docs/manual-ocaml/libref/Str.html#VALregexp 173 | -------------------------------------------------------------------------------- /doc/convert-readme.js: -------------------------------------------------------------------------------- 1 | const gfm = require('cmark-gfm-js'); 2 | const fs = require('fs'); 3 | 4 | const markdown = fs.readFileSync(0, 'utf-8'); 5 | const html = gfm.convert(markdown); 6 | console.log(html); 7 | -------------------------------------------------------------------------------- /doc/footer.html: -------------------------------------------------------------------------------- 1 |
2 | This page uses github-markdown-css. 3 |
4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /doc/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Bisect_ppx - Code coverage for OCaml 9 | 12 | 13 | 14 | 15 | 16 | 30 | 31 | 32 | 33 | 34 | Fork me on GitHub 35 | -------------------------------------------------------------------------------- /doc/logo-initial.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aantron/bisect_ppx/dc29402f2f8b6246150b18ac2e7e1d48e578a106/doc/logo-initial.jpg -------------------------------------------------------------------------------- /doc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aantron/bisect_ppx/dc29402f2f8b6246150b18ac2e7e1d48e578a106/doc/logo.png -------------------------------------------------------------------------------- /doc/logo0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aantron/bisect_ppx/dc29402f2f8b6246150b18ac2e7e1d48e578a106/doc/logo0.png -------------------------------------------------------------------------------- /doc/sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aantron/bisect_ppx/dc29402f2f8b6246150b18ac2e7e1d48e578a106/doc/sample.gif -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.7) 2 | (cram enable) 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bisect_ppx", 3 | "description": "Code coverage for OCaml and Reason", 4 | "version": "2.8.3", 5 | "license": "MIT", 6 | "homepage": "https://github.com/aantron/bisect_ppx", 7 | "bugs": { 8 | "url": "https://github.com/aantron/bisect_ppx/issues" 9 | }, 10 | "keywords": [ 11 | "ReScript", 12 | "reason", 13 | "coverage" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/aantron/bisect_ppx.git" 18 | }, 19 | "author": { 20 | "name": "Anton Bachin", 21 | "email": "antonbachin@yahoo.com", 22 | "url": "https://github.com/aantron" 23 | }, 24 | "scripts": { 25 | "preinstall": "bash src/npm-install.sh" 26 | }, 27 | "bin": { 28 | "bisect-ppx-report": "bisect-ppx-report" 29 | }, 30 | "files": [ 31 | "bisect_ppx.opam", 32 | "bsconfig.json", 33 | "dune-project", 34 | "binaries.esy.json", 35 | "src", 36 | "bin", 37 | "bisect-ppx-report" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /src/common/bisect_common.ml: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | (* Basic types and file [bisect*.coverage] file identifier. Shared with the 8 | reporter. *) 9 | 10 | type instrumented_file = { 11 | filename : string; 12 | points : int array; 13 | counts : int array; 14 | } 15 | 16 | type coverage = (string, instrumented_file) Hashtbl.t 17 | 18 | let coverage_file_identifier = "BISECT-COVERAGE-4" 19 | 20 | 21 | 22 | (* Output functions for the [bisect*.coverage] file format. *) 23 | 24 | let write_int buffer i = 25 | Buffer.add_char buffer ' '; 26 | Buffer.add_string buffer (string_of_int i) 27 | 28 | let write_string buffer s = 29 | Buffer.add_char buffer ' '; 30 | Buffer.add_string buffer (string_of_int (String.length s)); 31 | Buffer.add_char buffer ' '; 32 | Buffer.add_string buffer s 33 | 34 | let write_array write_element buffer a = 35 | Buffer.add_char buffer ' '; 36 | Buffer.add_string buffer (string_of_int (Array.length a)); 37 | Array.iter (write_element buffer) a 38 | 39 | let write_list write_element buffer l = 40 | Buffer.add_char buffer ' '; 41 | Buffer.add_string buffer (string_of_int (List.length l)); 42 | List.iter (write_element buffer) l 43 | 44 | let write_instrumented_file buffer {filename; points; counts} = 45 | write_string buffer filename; 46 | write_array write_int buffer points; 47 | write_array write_int buffer counts 48 | 49 | let write_coverage coverage = 50 | let buffer = Buffer.create 4096 in 51 | Buffer.add_string buffer coverage_file_identifier; 52 | write_list write_instrumented_file buffer coverage; 53 | Buffer.contents buffer 54 | 55 | 56 | 57 | (* Accumulated visit counts. This is used only by the native and ReScript 58 | runtimes. It is idly linked as part of this module into the PPX and reporter, 59 | as well, but not used by them. *) 60 | 61 | let coverage : coverage Lazy.t = 62 | lazy (Hashtbl.create 17) 63 | 64 | let register_file ~filename ~points = 65 | let filename = 66 | if Filename.check_suffix filename ".re.ml" then 67 | Filename.chop_suffix filename ".ml" 68 | else 69 | filename 70 | in 71 | let counts = Array.make (Array.length points) 0 in 72 | let coverage = Lazy.force coverage in 73 | if not (Hashtbl.mem coverage filename) then 74 | Hashtbl.add coverage filename {filename; points; counts}; 75 | `Visit (fun index -> 76 | let current_count = counts.(index) in 77 | if current_count < max_int then 78 | counts.(index) <- current_count + 1) 79 | 80 | 81 | 82 | let reset_counters () = 83 | Hashtbl.iter begin fun _ {counts; _} -> 84 | match Array.length counts with 85 | | 0 -> () 86 | | n -> Array.fill counts 0 (n - 1) 0 87 | end 88 | (Lazy.force coverage) 89 | 90 | 91 | 92 | (** Helpers for serializing the coverage data in {!coverage}. *) 93 | 94 | let flatten_coverage coverage = 95 | Hashtbl.fold (fun _ file acc -> file::acc) coverage [] 96 | 97 | let runtime_data_to_string () = 98 | match flatten_coverage (Lazy.force coverage) with 99 | | [] -> 100 | None 101 | | data -> 102 | Some (write_coverage data) 103 | 104 | let write_coverage coverage = 105 | write_coverage (flatten_coverage coverage) 106 | 107 | let prng = 108 | Random.State.make_self_init () [@coverage off] 109 | 110 | let random_filename ~prefix = 111 | prefix ^ 112 | (string_of_int (abs (Random.State.int prng 1000000000))) ^ 113 | ".coverage" 114 | -------------------------------------------------------------------------------- /src/common/bisect_common.mli: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | (** This module provides type definitions and functions that are shared between 8 | more than one part of Bisect, namely: 9 | 10 | - the instrumenter (PPX) 11 | - the native runtime ([Bisect.Runtime] in [src/runtime/native/]) 12 | - the ReScript runtime ([Bisect.Runtime] in [src/runtime/js/]) 13 | - the reporter ([bisect-ppx-report]) *) 14 | 15 | 16 | 17 | (** {1 Types} 18 | 19 | Types representing accumulated visit counts. This data is written to 20 | [bisect*.coverage] files when an instrumented binary exits. 21 | 22 | The two types in this section, {!instrumented_file} and {!coverage}, and the 23 | value {!coverage_file_identifier}, are the only items shared with the 24 | reporter. Otherwise, the reporter is self-contained. *) 25 | 26 | type instrumented_file = { 27 | filename : string; (** Source file name. *) 28 | points : int array; (** Byte offsets of the points placed in the file. *) 29 | counts : int array; (** Visitation counts, one for each point. *) 30 | } 31 | (** Data gathered for a single source file. *) 32 | 33 | type coverage = (string, instrumented_file) Hashtbl.t 34 | (** A binary instrumented with Bisect, when run, produces coverage statistics 35 | for each of its source files. The runtime and reporter both index the 36 | statistics by source file name. *) 37 | 38 | val coverage : coverage Lazy.t 39 | (** The accumulated coverage statistics. *) 40 | 41 | val coverage_file_identifier : string 42 | (** A string written at the beginning of each [bisect*.coverage] files. Provides 43 | a sanity check for the reporter that it is reading a [bisect*.coverage] 44 | file, and the file format version. *) 45 | 46 | 47 | 48 | (** {1 Initialization} *) 49 | 50 | val register_file : 51 | filename:string -> points:int array -> [`Visit of (int -> unit)] 52 | (** Each source file is instrumented to call {!Bisect.Runtime.register_file} at 53 | run time, during program initialization. {!Bisect.Runtime.register_file} 54 | eventually forwards to this function, {!Biesct_common.register_file}. This 55 | function allocates the visit count array, with one array cell for each 56 | point, and registers the array for later writing to [bisect*.coverage] 57 | files. 58 | 59 | - [~filename] is the name of the source file. 60 | - [~points] is the list of byte offsets of the instrumentation points placed 61 | in the source file. 62 | 63 | The return value is the function that is called by each instrumentation 64 | point to increment its own visit count. The instrumentation point passes its 65 | own index to the function. *) 66 | 67 | 68 | 69 | (** {1 [.coverage] output} *) 70 | 71 | val write_coverage : coverage -> string 72 | (** Converts the given coverage data to string. *) 73 | 74 | val runtime_data_to_string : unit -> string option 75 | (** Same as {!write_runtime_data}, but writes the output to a string instead. 76 | 77 | [None] is returned if there are no source files registered. This can occur 78 | when the runtime gets linked into a binary, but no files had been 79 | instrumented, because instrumentation was turned off. This combination 80 | normally shouldn't happen, but it can occur depending on the quality of the 81 | integration between the build system and Bisect. *) 82 | 83 | val reset_counters : unit -> unit 84 | (** Clears accumulated visit counts. All array cells are set to zero. *) 85 | 86 | val random_filename : prefix:string -> string 87 | (** Returns a random filename with the given prefix. *) 88 | -------------------------------------------------------------------------------- /src/common/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name bisect_common) 3 | (public_name bisect_ppx.common) 4 | (synopsis "Bisect_ppx internal functions (internal)")) 5 | -------------------------------------------------------------------------------- /src/npm-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | esy_build() { 4 | set -e 5 | set -x 6 | esy install -P binaries.esy.json 7 | esy -P binaries.esy.json dune build -p bisect_ppx src/ppx/js/ppx.exe 8 | cp _build/default/src/ppx/js/ppx.exe ./ppx 9 | esy -P binaries.esy.json dune build -p bisect_ppx src/report/main.exe 10 | cp _build/default/src/report/main.exe ./bisect-ppx-report 11 | exit 0 12 | } 13 | 14 | UNAME=`uname -s` 15 | RESULT=$? 16 | if [ "$RESULT" != 0 ] 17 | then 18 | echo "Cannot detect OS; falling back to a source build." 19 | esy_build 20 | fi 21 | 22 | case "$UNAME" in 23 | "Linux") OS=linux;; 24 | "Darwin") OS=macos;; 25 | *) echo "Unknown OS '$UNAME'; falling back to a source build."; esy_build;; 26 | esac 27 | 28 | if [ ! -f bin/$OS/ppx ] 29 | then 30 | echo "bin/$OS/ppx not found; falling back to a source build." 31 | esy_build 32 | fi 33 | 34 | if [ ! -f bin/$OS/bisect-ppx-report ] 35 | then 36 | echo "bin/$OS/bisect-ppx-report not found; falling back to a source build." 37 | esy_build 38 | fi 39 | 40 | bin/$OS/bisect-ppx-report --help plain > /dev/null 41 | RESULT=$? 42 | if [ "$RESULT" != 0 ] 43 | then 44 | echo "Pre-built binaries invalid; falling back to a source build." 45 | esy_build 46 | fi 47 | 48 | echo "Using pre-built binaries for system '$OS'." 49 | cp bin/$OS/ppx ./ppx 50 | cp bin/$OS/bisect-ppx-report ./bisect-ppx-report 51 | -------------------------------------------------------------------------------- /src/ppx/dune: -------------------------------------------------------------------------------- 1 | (ocamllex exclude_lexer) 2 | (ocamlyacc exclude_parser) 3 | 4 | (library 5 | (name bisect_ppx) 6 | (public_name bisect_ppx) 7 | (kind ppx_rewriter) 8 | (instrumentation.backend (ppx bisect_ppx)) 9 | (synopsis "Code coverage for OCaml") 10 | (ppx_runtime_libraries bisect_ppx.runtime) 11 | (preprocess (pps ppxlib.metaquot)) 12 | (flags (:standard -open Ocaml_shadow)) 13 | (libraries bisect_ppx.common ppxlib str)) 14 | -------------------------------------------------------------------------------- /src/ppx/exclude.ml: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | exception Exception of (int * string) 8 | 9 | type t = 10 | | Name of string 11 | | Regexp of Str.regexp 12 | 13 | type file = { 14 | path : t; 15 | exclusions : t list option; 16 | } 17 | -------------------------------------------------------------------------------- /src/ppx/exclude.mli: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | (** This modules defines the types related to exlusion as stored in 8 | files. *) 9 | 10 | exception Exception of (int * string) 11 | (** The exception raised by either the lexer, or the parser. *) 12 | 13 | type t = 14 | | Name of string (** The exclusion is specified through an exact name. *) 15 | | Regexp of Str.regexp (** The exclusion is specified through a regular expression over names. *) 16 | (** The type of an exclusion. *) 17 | 18 | type file = { 19 | path : t; (** The path to the file. *) 20 | exclusions : t list option; (** The list of exclusions. *) 21 | } 22 | (** The type describing the contents of an exclusion file. *) 23 | -------------------------------------------------------------------------------- /src/ppx/exclude_lexer.mll: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | { 8 | 9 | type error = 10 | | Invalid_character of char 11 | | Unexpected_end_of_file 12 | 13 | let string_of_error = function 14 | | Invalid_character ch -> Printf.sprintf "invalid character %C" ch 15 | | Unexpected_end_of_file -> "unexpected end of file" 16 | 17 | let fail lexbuf error = 18 | let open Lexing in 19 | let pos = lexbuf.lex_curr_p in 20 | raise (Exclude.Exception (pos.pos_lnum, string_of_error error)) 21 | 22 | let incr_line lexbuf = 23 | let open Lexing in 24 | let pos = lexbuf.lex_curr_p in 25 | lexbuf.lex_curr_p <- { pos with pos_lnum = succ pos.pos_lnum; 26 | pos_bol = pos.pos_cnum } 27 | 28 | let add_char prefix buf str = 29 | Buffer.add_char buf (Char.chr (int_of_string (prefix ^ str))) 30 | 31 | let add_octal_char = add_char "0o" 32 | 33 | let add_hexa_char = add_char "0x" 34 | 35 | } 36 | 37 | let eol = ('\010' | '\013' |"\013\010" | "\010\013") 38 | 39 | let whitespace = [' ' '\t'] 40 | 41 | let letter = [ 'a'-'z' 'A'-'Z' '\192'-'\214' '\216'-'\246' '\248'-'\255' ] 42 | 43 | let decimal_digit = [ '0'-'9' ] 44 | 45 | let decimal = decimal_digit+ 46 | 47 | let octal_digit = [ '0'-'7' ] 48 | 49 | let octal = octal_digit octal_digit octal_digit 50 | 51 | let hexa_digit = [ '0'-'9' 'a'-'f' 'A'-'F' ] 52 | 53 | let hexa = hexa_digit hexa_digit 54 | 55 | let ident = letter (letter | decimal_digit | ['_'] | ['-'])* 56 | 57 | rule token = parse 58 | | "]" { Exclude_parser.CLOSING_BRACKET } 59 | | "[" { Exclude_parser.OPENING_BRACKET } 60 | | ";" { Exclude_parser.SEMICOLON } 61 | | "file" { Exclude_parser.FILE } 62 | | "name" { Exclude_parser.NAME } 63 | | "regexp" { Exclude_parser.REGEXP } 64 | | "\"" { string 0 (Buffer.create 64) lexbuf } 65 | | "(*" { comment 1 lexbuf } 66 | | whitespace+ { token lexbuf } 67 | | eol { incr_line lexbuf; token lexbuf } 68 | | eof { Exclude_parser.EOF } 69 | | _ as ch { fail lexbuf (Invalid_character ch) } 70 | and string n strbuf = parse 71 | | "\\b" { Buffer.add_char strbuf '\008'; string n strbuf lexbuf } 72 | | "\\t" { Buffer.add_char strbuf '\009'; string n strbuf lexbuf } 73 | | "\\n" { Buffer.add_char strbuf '\010'; string n strbuf lexbuf } 74 | | "\\r" { Buffer.add_char strbuf '\013'; string n strbuf lexbuf } 75 | | "\\\'" { Buffer.add_char strbuf '\''; string n strbuf lexbuf } 76 | | "\\\"" { Buffer.add_char strbuf '\"'; string n strbuf lexbuf } 77 | | "\\\\" { Buffer.add_char strbuf '\\'; string n strbuf lexbuf } 78 | | "\\" octal as o { add_octal_char strbuf o; string n strbuf lexbuf } 79 | | "\\x" hexa as h { add_hexa_char strbuf h; string n strbuf lexbuf } 80 | | "\"" { if n = 0 then 81 | Exclude_parser.STRING (Buffer.contents strbuf) 82 | else 83 | comment n lexbuf } 84 | | _ as c { Buffer.add_char strbuf c; string n strbuf lexbuf } 85 | and comment n = parse 86 | | "(*" { comment (succ n) lexbuf } 87 | | "*)" { if n = 1 then token lexbuf else comment (pred n) lexbuf } 88 | | "\"" { string n (Buffer.create 64) lexbuf } 89 | | eol { incr_line lexbuf; comment n lexbuf } 90 | | eof { fail lexbuf Unexpected_end_of_file } 91 | | _ { comment n lexbuf } 92 | -------------------------------------------------------------------------------- /src/ppx/exclude_parser.mly: -------------------------------------------------------------------------------- 1 | /* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. */ 4 | 5 | 6 | 7 | %{ 8 | 9 | type error = 10 | | Invalid_file_contents 11 | | Invalid_file_declaration 12 | | Invalid_exclusion 13 | | Invalid_regular_expression of string 14 | 15 | let string_of_error = function 16 | | Invalid_file_contents -> "invalid file contents" 17 | | Invalid_file_declaration -> "invalid file declaration" 18 | | Invalid_exclusion -> "invalid exclusion" 19 | | Invalid_regular_expression re -> Printf.sprintf "invalid regular expression %S" re 20 | 21 | let fail error = 22 | let pos = Parsing.symbol_start_pos () in 23 | let line = pos.Lexing.pos_lnum in 24 | raise (Exclude.Exception (line, string_of_error error)) 25 | 26 | let make_regexp s = 27 | try Str.regexp s 28 | with _ -> fail (Invalid_regular_expression s) 29 | 30 | %} 31 | 32 | %token CLOSING_BRACKET OPENING_BRACKET 33 | %token SEMICOLON FILE NAME REGEXP EOF 34 | %token STRING 35 | 36 | %start file 37 | %type file 38 | 39 | %% 40 | 41 | file: file_decl_list EOF { List.rev $1 } 42 | | error { fail Invalid_file_contents } 43 | 44 | file_decl_list: /* epsilon */ { [] } 45 | | file_decl_list file_decl { $2 :: $1 } 46 | 47 | file_decl: FILE file_pattern exclusion_list separator_opt 48 | { { Exclude.path = $2; 49 | Exclude.exclusions = $3; } } 50 | | FILE error { fail Invalid_file_declaration } 51 | 52 | file_pattern: 53 | | STRING { Exclude.Name $1 } 54 | | REGEXP STRING { Exclude.Regexp (make_regexp $2) } 55 | 56 | exclusion_list : 57 | | { None } 58 | | OPENING_BRACKET exclusions CLOSING_BRACKET 59 | { Some (List.rev $2) } 60 | 61 | exclusions : 62 | | { [] } 63 | | exclusions exclusion { $2::$1 } 64 | 65 | exclusion: NAME STRING separator_opt 66 | { Exclude.Name $2 } 67 | | REGEXP STRING separator_opt { Exclude.Regexp (make_regexp $2) } 68 | | error { fail Invalid_exclusion } 69 | 70 | separator_opt: /* epsilon */ { } 71 | | SEMICOLON { } 72 | 73 | %% 74 | -------------------------------------------------------------------------------- /src/ppx/exclusions.ml: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | type t = 8 | | Regular_expression of Str.regexp 9 | | Exclude_file of Exclude.file 10 | 11 | let excluded = ref [] 12 | 13 | let function_separator = Str.regexp "[ \t]*,[ \t]*" [@coverage off] 14 | 15 | let add s = 16 | let patterns = Str.split function_separator s in 17 | let patterns = List.map (fun x -> Regular_expression (Str.regexp x)) patterns in 18 | excluded := patterns @ !excluded 19 | 20 | let add_file s = 21 | let pattern = Exclude.{path = Regexp (Str.regexp s); exclusions = None} in 22 | excluded := (Exclude_file pattern)::!excluded 23 | 24 | let add_from_channel filename ch = 25 | let lexbuf = Lexing.from_channel ch in 26 | try 27 | let res = Exclude_parser.file Exclude_lexer.token lexbuf in 28 | let res = List.map (fun x -> Exclude_file x) res in 29 | excluded := res @ !excluded; 30 | close_in_noerr ch 31 | with 32 | | Exclude.Exception (line, msg) -> 33 | Printf.eprintf " *** error in file %S at line %d: %s\n" 34 | filename line msg; 35 | close_in_noerr ch; 36 | exit 1 37 | | e -> 38 | close_in_noerr ch; 39 | raise e 40 | 41 | let add_from_file filename = 42 | (* ReScript runs the PPX from PROJECT_ROOT/lib/bs. *) 43 | let cwd = Sys.getcwd () in 44 | let parent = Filename.basename cwd in 45 | let grandparent = Filename.(basename (dirname cwd)) in 46 | 47 | let channel = 48 | if grandparent = "lib" && parent = "bs" then 49 | try open_in (Filename.(concat (dirname (dirname cwd))) filename) 50 | with Sys_error _ -> open_in filename 51 | else 52 | open_in filename 53 | in 54 | add_from_channel filename channel 55 | 56 | let match_pattern pattern name = 57 | Str.string_match pattern name 0 && Str.match_end () = String.length name 58 | 59 | let file_name_matches exclusion file = 60 | match exclusion.Exclude.path with 61 | | Exclude.Name file' -> file = file' 62 | | Exclude.Regexp pattern -> match_pattern pattern file 63 | 64 | let contains_value file name = 65 | List.exists 66 | (function 67 | | Regular_expression patt -> 68 | match_pattern patt name 69 | | Exclude_file ef -> 70 | let in_value_list () = 71 | match ef.Exclude.exclusions with 72 | | None -> true 73 | | Some exclusions -> 74 | exclusions |> List.exists (function 75 | | Exclude.Name en -> name = en 76 | | Exclude.Regexp patt -> match_pattern patt name) 77 | in 78 | file_name_matches ef file && in_value_list ()) 79 | !excluded 80 | 81 | let contains_file file = 82 | !excluded |> List.exists (function 83 | | Regular_expression _ -> false 84 | | Exclude_file exclusion -> 85 | exclusion.Exclude.exclusions = None && file_name_matches exclusion file) 86 | -------------------------------------------------------------------------------- /src/ppx/exclusions.mli: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | (** This stateful module contains the information about exluded toplevel 8 | declarations. *) 9 | 10 | 11 | val add : string -> unit 12 | (** Adds a list of comma-separated elements to excluded list. *) 13 | 14 | val add_file : string -> unit 15 | (** Adds a filename pattern to the list of files to exclude. *) 16 | 17 | val add_from_file : string -> unit 18 | (** Adds exclusions from the passed file to excluded list. 19 | 20 | Raises [Sys_error] if an i/o error occurs, [Exclude.Exception] if 21 | an error occurs while parsing the file. *) 22 | 23 | val contains_value : string -> string -> bool 24 | (** [contains_value file name] tests whether toplevel value with name 25 | [name] from file [file] is in excluded list. *) 26 | 27 | val contains_file : string -> bool 28 | (** [contains_file file] tests whether the entire file with name [name] is in 29 | the excluded {e files} list. A file is completely excluded (and not 30 | instrumented) when a list of excluded top-level values is not given for that 31 | file at all. *) 32 | -------------------------------------------------------------------------------- /src/ppx/instrument.mli: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | class instrumenter : object 8 | inherit Ppxlib.Ast_traverse.map_with_expansion_context_and_errors 9 | 10 | method transform_impl_file: 11 | Ppxlib.Expansion_context.Base.t -> 12 | Ppxlib.Parsetree.structure -> 13 | Ppxlib.Parsetree.structure 14 | end 15 | (** This class implements an instrumenter to be used through the {i -ppx} 16 | command-line switch. *) 17 | 18 | val bisect_file : string option ref 19 | (** Default value for [BISECT_FILE]. *) 20 | 21 | val bisect_silent : string option ref 22 | (** Default value for [BISECT_SILENT]. *) 23 | 24 | val bisect_sigterm : bool ref 25 | (** Default value for [BISECT_SIGTERM]. *) 26 | -------------------------------------------------------------------------------- /src/ppx/js/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name ppx) 3 | (libraries bisect_ppx ppxlib)) 4 | -------------------------------------------------------------------------------- /src/ppx/js/ppx.ml: -------------------------------------------------------------------------------- 1 | let _ = Bisect_ppx.Register.conditional := true 2 | 3 | let () = Ppxlib.Driver.run_as_ppx_rewriter () 4 | -------------------------------------------------------------------------------- /src/ppx/register.ml: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | let conditional = ref false 8 | 9 | let enabled () = 10 | match !conditional with 11 | | false -> 12 | `Enabled 13 | | true -> 14 | match Sys.getenv "BISECT_ENABLE" with 15 | | exception Not_found -> 16 | `Disabled 17 | | s when String.uppercase_ascii s = "YES" -> 18 | `Enabled 19 | | _ -> 20 | `Disabled 21 | 22 | let conditional_exclude_file filename = 23 | match enabled () with 24 | | `Enabled -> Exclusions.add_from_file filename 25 | | `Disabled -> () 26 | 27 | let switches = [ 28 | ("--exclude-files", 29 | Arg.String Exclusions.add_file, 30 | " Exclude files matching "); 31 | 32 | ("--exclusions", 33 | Arg.String conditional_exclude_file, 34 | " Exclude functions listed in given file"); 35 | 36 | ("--conditional", 37 | Arg.Set conditional, 38 | " Instrument only when BISECT_ENABLE is YES"); 39 | 40 | ("--bisect-file", 41 | Arg.String (fun s -> Instrument.bisect_file := Some s), 42 | " Default value for BISECT_FILE environment variable"); 43 | 44 | ("--bisect-silent", 45 | Arg.String (fun s -> Instrument.bisect_silent := Some s), 46 | " Default value for BISECT_SILENT environment variable"); 47 | 48 | ("--bisect-sigterm", 49 | Arg.Set Instrument.bisect_sigterm, 50 | (" Install a signal handler writing coverage data and" ^ 51 | " terminating on reception of SIGTERM")); 52 | ] 53 | 54 | let () = 55 | Arg.align switches 56 | |> List.iter (fun (key, spec, doc) -> Ppxlib.Driver.add_arg key spec ~doc) 57 | 58 | 59 | let () = 60 | let impl ctxt ast = 61 | match enabled () with 62 | | `Enabled -> 63 | new Instrument.instrumenter#transform_impl_file ctxt ast 64 | | `Disabled -> 65 | ast 66 | in 67 | let instrument = Ppxlib.Driver.Instrument.V2.make impl ~position:After in 68 | Ppxlib.Driver.register_transformation ~instrument "bisect_ppx" 69 | -------------------------------------------------------------------------------- /src/report/cobertura.ml: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | type line = { 8 | number : int; 9 | hits : int 10 | } 11 | 12 | type class_ = { 13 | name : string; 14 | line_rate : float; 15 | lines : line list; 16 | } 17 | 18 | type package = { 19 | name : string; 20 | line_rate : float; 21 | classes : class_ list; 22 | } 23 | 24 | type packages = { 25 | packages : package list; 26 | } 27 | 28 | type cobertura = { 29 | lines_valid : int; 30 | lines_covered : int; 31 | line_rate : float; 32 | sources : string list; 33 | packages : packages; 34 | } 35 | 36 | let pp_list pp fmt = 37 | List.iter (fun x -> pp fmt x; Format.pp_print_string fmt "\n") 38 | 39 | let pp_line fmt {number; hits} = 40 | Format.fprintf fmt " " number hits 41 | 42 | let pp_lines fmt lines = 43 | let open Format in 44 | fprintf fmt " \n%a \n" 45 | (pp_list pp_line) lines 46 | 47 | let pp_class_ fmt {name; line_rate; lines} = 48 | let open Format in 49 | let class_infos = 50 | Format.sprintf "name=\"%s\" filename=\"%s\" line-rate=\"%f\"" 51 | name 52 | name 53 | line_rate 54 | in 55 | fprintf fmt 56 | " \n%a " 57 | class_infos 58 | pp_lines lines 59 | 60 | let pp_classes fmt classes = 61 | let open Format in 62 | fprintf fmt 63 | " \n%a \n" 64 | (pp_list pp_class_) classes 65 | 66 | let pp_package fmt {name; line_rate; classes} = 67 | let open Format in 68 | let package_infos = 69 | Format.sprintf {|name="%s" line-rate="%f"|} 70 | name 71 | line_rate 72 | in 73 | fprintf fmt " \n%a " 74 | package_infos 75 | pp_classes classes 76 | 77 | let pp_packages fmt ({packages} : packages) = 78 | let open Format in 79 | fprintf fmt " \n%a \n" 80 | (pp_list pp_package) packages 81 | 82 | let pp_source fmt source = 83 | Format.fprintf fmt " %s" source 84 | 85 | let pp_sources fmt sources = 86 | let open Format in 87 | fprintf fmt 88 | " \n%a \n" 89 | (pp_list pp_source) sources 90 | 91 | let pp_cobertura fmt ({sources; packages; _} as cobertura) = 92 | let open Format in 93 | let cobertura_infos {lines_valid; lines_covered; line_rate; _} = 94 | sprintf 95 | {|lines-valid="%d" lines-covered="%d" line-rate="%f"|} 96 | lines_valid 97 | lines_covered 98 | line_rate 99 | in 100 | fprintf fmt 101 | "\n\n%a%a" 102 | (cobertura_infos cobertura) 103 | pp_sources sources 104 | pp_packages packages 105 | 106 | let line_rate (visited, total) = 107 | float_of_int visited /. float_of_int total 108 | 109 | let update_counts counts line_counts = 110 | List.fold_left (fun ((visited, total) as acc) -> function 111 | | None -> acc 112 | | Some x when x > 0 -> (visited + 1, total + 1) 113 | | Some _ -> (visited, total + 1)) 114 | counts line_counts 115 | 116 | let line line hits = 117 | {number = line; hits} 118 | 119 | let classes ~global_counts resolver coverage : class_ list = 120 | let class_ {Bisect_common.filename; points; counts} = 121 | match resolver ~filename with 122 | | None -> 123 | None 124 | | Some resolved_in_file -> 125 | let line_counts = 126 | Util.line_counts ~filename:resolved_in_file ~points ~counts in 127 | global_counts := update_counts !global_counts line_counts; 128 | let line_rate = line_rate (update_counts (0, 0) line_counts) in 129 | 130 | let i = ref 1 in 131 | let lines = 132 | List.fold_left (fun acc x -> 133 | let line = 134 | match x with 135 | | None -> None 136 | | Some nb -> 137 | Some (line !i nb) 138 | in 139 | let () = incr i in 140 | match line with 141 | | None -> acc 142 | | Some line -> line::acc) 143 | [] 144 | line_counts 145 | |> List.rev 146 | in 147 | 148 | Some {name = filename; line_rate; lines} 149 | in 150 | 151 | Hashtbl.fold (fun _ file acc -> 152 | match class_ file with 153 | | None -> acc 154 | | Some x -> x::acc) 155 | coverage 156 | [] 157 | 158 | let package ~counts ~resolver ~coverage = 159 | let classes = classes ~global_counts:counts resolver coverage in 160 | let line_rate = line_rate !counts in 161 | {name = "."; line_rate; classes} 162 | 163 | let packages ~packages = {packages} 164 | 165 | let cobertura ~resolver ~coverage = 166 | let counts = ref (0, 0) in 167 | let package = package ~counts ~resolver ~coverage in 168 | let packages = packages ~packages:[package] in 169 | let sources = ["."] in 170 | let rate = line_rate !counts in 171 | { 172 | lines_valid = snd !counts; 173 | lines_covered = fst !counts; 174 | line_rate = rate; 175 | packages; 176 | sources; 177 | } 178 | 179 | let output 180 | ~to_file ~coverage_files ~coverage_paths ~source_paths ~ignore_missing_files 181 | ~expect ~do_not_expect = 182 | 183 | let coverage = 184 | Input.load_coverage 185 | ~coverage_files ~coverage_paths ~expect ~do_not_expect in 186 | let resolver = 187 | Util.find_source_file ~source_roots:source_paths ~ignore_missing_files in 188 | let cobertura = cobertura ~resolver ~coverage in 189 | let () = Util.mkdirs (Filename.dirname to_file) in 190 | let oc = 191 | try open_out to_file 192 | with Sys_error message -> 193 | Util.fatal "cannot open output file '%s': %s" to_file message 194 | in 195 | try 196 | let fmt = Format.formatter_of_out_channel oc in 197 | let () = pp_cobertura fmt cobertura in 198 | close_out oc 199 | with 200 | | Sys_error message -> 201 | Util.fatal "cannot write output file '%s': %s" to_file message 202 | | exn -> 203 | close_out_noerr oc; 204 | raise exn 205 | -------------------------------------------------------------------------------- /src/report/cobertura.mli: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | val output : 8 | to_file:string -> 9 | coverage_files:string list -> 10 | coverage_paths:string list -> 11 | source_paths:string list -> 12 | ignore_missing_files:bool -> 13 | expect:string list -> 14 | do_not_expect:string list -> 15 | unit 16 | -------------------------------------------------------------------------------- /src/report/coverage.js: -------------------------------------------------------------------------------- 1 | function tool_tip_element() 2 | { 3 | var element = document.querySelector("#tool-tip"); 4 | if (element === null) { 5 | element = document.createElement("div"); 6 | element.id = "tool-tip"; 7 | document.querySelector("body").appendChild(element); 8 | } 9 | 10 | return element; 11 | }; 12 | 13 | var tool_tip = tool_tip_element(); 14 | var html = document.getElementsByTagName("html")[0]; 15 | 16 | function attach_tool_tip() 17 | { 18 | document.querySelector("body").onmousemove = function (event) 19 | { 20 | var element = event.target; 21 | if (element.dataset.count === undefined) 22 | element = event.target.parentNode; 23 | 24 | if (element.dataset.count && element.dataset.count !== "0") { 25 | tool_tip.textContent = element.dataset.count; 26 | tool_tip.classList.add("visible"); 27 | 28 | if (event.clientY < html.clientHeight - 48) 29 | tool_tip.style.top = event.clientY + 7 + "px"; 30 | else 31 | tool_tip.style.top = event.clientY - 32 + "px"; 32 | 33 | tool_tip.style.left = event.clientX + 7 + "px"; 34 | } 35 | else 36 | tool_tip.classList.remove("visible"); 37 | } 38 | }; 39 | 40 | attach_tool_tip(); 41 | 42 | function move_line_to_cursor(cursor_y, line_number) 43 | { 44 | var id = "L" + line_number; 45 | 46 | var line_anchor = 47 | document.querySelector("a[id=" + id + "] + span"); 48 | if (line_anchor === null) 49 | return; 50 | 51 | var line_y = line_anchor.getBoundingClientRect().top + 18; 52 | 53 | var y = window.scrollY; 54 | window.location = "#" + id; 55 | window.scrollTo(0, y + line_y - cursor_y); 56 | }; 57 | 58 | function handle_navbar_clicks() 59 | { 60 | var line_count = document.querySelectorAll("a[id]").length; 61 | var navbar = document.querySelector("#navbar"); 62 | 63 | if (navbar === null) 64 | return; 65 | 66 | navbar.onclick = function (event) 67 | { 68 | event.preventDefault(); 69 | 70 | var line_number = 71 | Math.floor(event.clientY / navbar.clientHeight * line_count + 1); 72 | 73 | move_line_to_cursor(event.clientY, line_number); 74 | }; 75 | }; 76 | 77 | handle_navbar_clicks(); 78 | 79 | function handle_line_number_clicks() 80 | { 81 | document.querySelector("body").onclick = function (event) 82 | { 83 | if (event.target.tagName != "A") 84 | return; 85 | 86 | var line_number_location = event.target.href.search(/#L[0-9]+\$/); 87 | if (line_number_location === -1) 88 | return; 89 | 90 | var anchor = event.target.href.slice(line_number_location); 91 | 92 | event.preventDefault(); 93 | 94 | var y = window.scrollY; 95 | window.location = anchor; 96 | window.scrollTo(0, y); 97 | }; 98 | }; 99 | 100 | handle_line_number_clicks(); 101 | 102 | function handle_collapsible_click() 103 | { 104 | document.querySelectorAll("summary").forEach( 105 | function (summary) 106 | { 107 | summary.onclick = function (event) 108 | { 109 | var details = summary.parentElement; 110 | 111 | var all_open = function (sub_details) { 112 | var all_are_open = true; 113 | for (let details of sub_details) { 114 | all_are_open = 115 | all_are_open && 116 | details.hasAttribute('open'); 117 | } 118 | return all_are_open; 119 | }; 120 | 121 | var all_toggle = function (sub_details, toggle) { 122 | for (let details of sub_details) { 123 | if (toggle) 124 | details.removeAttribute('open'); 125 | else 126 | details.setAttribute('open', ''); 127 | } 128 | }; 129 | 130 | // ctrl-click toggles the state of the folder and all sub-folders, recursively: 131 | // - if all sub-folders are opened, then all sub-folders are closed 132 | // - if at least one sub-folder is closed (or the folder itself), 133 | // then all sub-folders are opened 134 | if (event.ctrlKey) { 135 | var sub_details = Array.prototype.slice.call( 136 | details.querySelectorAll("details") 137 | ); 138 | sub_details.push(details); 139 | all_toggle(sub_details, all_open(sub_details)); 140 | return false; 141 | } 142 | 143 | // shift-click toggles the state of all immediate sub-folders: 144 | // - if the folder is closed, just open it 145 | // - if the folder is opened: 146 | // - if all sub-folders are opened, then all sub-folders are closed 147 | // - if at least one sub-folder is closed, then all sub-folders are opened 148 | if (event.shiftKey && details.hasAttribute('open')) { 149 | details.setAttribute('open', ''); 150 | var sub_details = 151 | Array.prototype.filter.call( 152 | details.querySelectorAll("details"), 153 | function (sub_details) { 154 | return sub_details.parentNode === details; 155 | } 156 | ); 157 | all_toggle(sub_details, all_open(sub_details)); 158 | return false; 159 | } 160 | }; 161 | }); 162 | } 163 | 164 | handle_collapsible_click(); 165 | -------------------------------------------------------------------------------- /src/report/coveralls.mli: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | (** This module defines the output to Coveralls JSON. *) 8 | 9 | 10 | val output : 11 | to_file:string -> 12 | service_name:string -> 13 | service_number:string -> 14 | service_job_id:string -> 15 | service_pull_request:string -> 16 | repo_token:string -> 17 | git:bool -> 18 | parallel:bool -> 19 | coverage_files:string list -> 20 | coverage_paths:string list -> 21 | source_paths:string list -> 22 | ignore_missing_files:bool -> 23 | expect:string list -> 24 | do_not_expect:string list -> 25 | unit 26 | 27 | val output_and_send : 28 | service:[ `Codecov | `Coveralls ] -> 29 | service_name:string -> 30 | service_number:string -> 31 | service_job_id:string -> 32 | service_pull_request:string -> 33 | repo_token:string -> 34 | git:bool -> 35 | parallel:bool -> 36 | dry_run:bool -> 37 | coverage_files:string list -> 38 | coverage_paths:string list -> 39 | source_paths:string list -> 40 | ignore_missing_files:bool -> 41 | expect:string list -> 42 | do_not_expect:string list -> 43 | unit 44 | -------------------------------------------------------------------------------- /src/report/dummy-binary: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This dummy binary file is used to satisfy pnpm. See 4 | # https://github.com/aantron/bisect_ppx/issues/381. 5 | 6 | echo "Error: bisect-ppx-report was not installed" 7 | exit 1 8 | -------------------------------------------------------------------------------- /src/report/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name main) 3 | (public_name bisect-ppx-report) 4 | (package bisect_ppx) 5 | (libraries bisect_ppx.common cmdliner unix)) 6 | 7 | (rule 8 | (targets assets.ml) 9 | (deps 10 | (:css coverage.css) 11 | (:js coverage.js) 12 | (:hljs ../vendor/highlight.js/highlight.pack.js)) 13 | (action 14 | (with-stdout-to %{targets} 15 | (progn 16 | (echo "let css = {css|") 17 | (cat %{css}) 18 | (echo "|css}") 19 | (echo "let js = {js|") 20 | (cat %{js}) 21 | (echo "|js}") 22 | (echo "let highlight_js = {js|") 23 | (cat %{hljs}) 24 | (echo "|js}"))))) 25 | -------------------------------------------------------------------------------- /src/report/html.mli: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | val output : 8 | to_directory:string -> 9 | title:string -> 10 | tab_size:int -> 11 | theme:[ `Light | `Dark | `Auto ] -> 12 | coverage_files:string list -> 13 | coverage_paths:string list -> 14 | source_paths:string list -> 15 | ignore_missing_files:bool -> 16 | expect:string list -> 17 | do_not_expect:string list -> 18 | tree:bool -> 19 | sort_by_stats:bool -> 20 | unit 21 | -------------------------------------------------------------------------------- /src/report/input.mli: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | val load_coverage : 8 | coverage_files:string list -> 9 | coverage_paths:string list -> 10 | expect:string list -> 11 | do_not_expect:string list -> 12 | Bisect_common.coverage 13 | (** Loads the given [~coverage_files], and any [.coverage] files found under the 14 | given [~coverage_paths]. Returns the per-source coverage data, accumulated 15 | across all the [.coverage] files. 16 | 17 | [~expect] is a list of expected source files and/or source directories that 18 | should appear in the returned coverage data. [~do_not_expect] subtracts some 19 | files and directories from [~expect]. 20 | 21 | Any I/O errors that occur during this function are considered fatal, as the 22 | [.coverage] files have already been found. Failure to open such a file is 23 | probably a permissions error, a race condition with another process, or 24 | another serious and unusual condition. A missing source file, relative to 25 | the set given by [~expect], is also a fatal error, because the user has 26 | explicitly stated that the source file should be represented in the 27 | report. *) 28 | -------------------------------------------------------------------------------- /src/report/merge.ml: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | let output ~to_file ~coverage_files ~coverage_paths = 7 | let coverage = 8 | Input.load_coverage 9 | ~coverage_files ~coverage_paths ~expect:[] ~do_not_expect:[] 10 | |> Bisect_common.write_coverage 11 | in 12 | let () = Util.mkdirs (Filename.dirname to_file) in 13 | let oc = open_out to_file in 14 | try 15 | output_string oc coverage; 16 | close_out oc 17 | with exn -> 18 | close_out_noerr oc; 19 | raise exn 20 | -------------------------------------------------------------------------------- /src/report/merge.mli: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | (** This module merges coverage files into a single coverage file, summing counters pointwise *) 8 | 9 | 10 | val output : 11 | to_file:string -> 12 | coverage_files:string list -> 13 | coverage_paths:string list -> 14 | unit 15 | -------------------------------------------------------------------------------- /src/report/text.ml: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | let output ~per_file ~coverage_files ~coverage_paths ~expect ~do_not_expect = 8 | let coverage = 9 | Input.load_coverage 10 | ~coverage_files ~coverage_paths ~expect ~do_not_expect in 11 | 12 | let stats = 13 | Hashtbl.fold (fun _ (file : Bisect_common.instrumented_file) acc -> 14 | let total = Array.length file.counts in 15 | let visited = 16 | Array.fold_left 17 | (fun acc count -> if count > 0 then acc + 1 else acc) 0 file.counts 18 | in 19 | (file.filename, visited, total)::acc) coverage [] 20 | in 21 | 22 | let percentage numerator denominator = 23 | if denominator > 0 then 24 | let p = 25 | ((float_of_int numerator) *. 100.) /. (float_of_int denominator) in 26 | Printf.sprintf "%.2f" p 27 | else 28 | "100.00" 29 | in 30 | 31 | let second (_, v, _) = v in 32 | let third (_, _, v) = v in 33 | 34 | let total projection = 35 | stats 36 | |> List.map projection 37 | |> List.fold_left (+) 0 38 | in 39 | let visited_total = total second in 40 | let overall_total = total third in 41 | 42 | if per_file then begin 43 | let digits i = 44 | let rec loop bound count = 45 | if bound > i then 46 | count 47 | else 48 | loop (bound * 10) (count + 1) 49 | in 50 | loop 10 1 51 | in 52 | let digits projection = 53 | ("", visited_total, overall_total)::stats 54 | |> List.map projection 55 | |> List.map digits 56 | |> List.fold_left max 1 57 | in 58 | let visited_digits = digits second in 59 | let total_digits = digits third in 60 | 61 | stats 62 | |> List.sort (fun (file, _, _) (file', _, _) -> String.compare file file') 63 | |> List.iter begin fun (name, visited, total) -> 64 | Printf.printf "%6s %% %*i/%-*i %s\n" 65 | (percentage visited total) 66 | visited_digits visited 67 | total_digits total 68 | name 69 | end; 70 | 71 | Printf.printf "%6s %% %i/%i Project coverage\n%!" 72 | (percentage visited_total overall_total) visited_total overall_total 73 | end 74 | else 75 | Printf.printf "Coverage: %i/%i (%s%%)\n%!" 76 | visited_total overall_total (percentage visited_total overall_total) 77 | -------------------------------------------------------------------------------- /src/report/text.mli: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | val output : 8 | per_file:bool -> 9 | coverage_files:string list -> 10 | coverage_paths:string list -> 11 | expect:string list -> 12 | do_not_expect:string list -> 13 | unit 14 | -------------------------------------------------------------------------------- /src/report/util.ml: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | let verbose = 8 | ref false 9 | 10 | let info arguments = 11 | Printf.ksprintf (fun s -> 12 | if !verbose then 13 | Printf.printf "Info: %s\n%!" s) arguments 14 | 15 | let fatal arguments = 16 | Printf.ksprintf (fun s -> 17 | Printf.eprintf "Error: %s\n%!" s; exit 1) arguments 18 | 19 | 20 | 21 | let split f list = 22 | let rec split acc list = 23 | match list with 24 | | head::tail -> 25 | if f head then split (head::acc) tail 26 | else (List.rev acc), list 27 | | [] -> 28 | (List.rev acc), [] 29 | in 30 | split [] list 31 | 32 | 33 | 34 | let mkdirs directory = 35 | let rec make directory = 36 | if not (Sys.file_exists directory) then begin 37 | make (Filename.dirname directory); 38 | Unix.mkdir directory 0o755 39 | end in 40 | try make directory 41 | with Unix.(Unix_error (error, _, path)) -> 42 | fatal "cannot create directory '%s': %s" path (Unix.error_message error) 43 | 44 | let workspace_root = 45 | lazy begin 46 | let rec loop path = 47 | let parent = Filename.dirname path in 48 | let parent_result = 49 | if parent <> path && not (Filename.is_relative parent) then 50 | loop parent 51 | else 52 | None 53 | in 54 | match parent_result with 55 | | Some _ -> parent_result 56 | | None -> 57 | if Sys.file_exists (Filename.concat path "dune-workspace") then 58 | Some path 59 | else 60 | None 61 | in 62 | loop (Sys.getcwd ()) 63 | end 64 | 65 | let find_dune_workspace_root () = 66 | Lazy.force workspace_root 67 | 68 | let find_source_file ~source_roots ~ignore_missing_files ~filename = 69 | let fail () = 70 | let message = 71 | source_roots 72 | |> List.map (Printf.sprintf " - %s") 73 | |> fun text -> 74 | (Printf.sprintf "cannot find source file '%s' in:" filename)::text 75 | |> String.concat "\n" 76 | in 77 | if ignore_missing_files then begin 78 | info "%s" message; 79 | None 80 | end 81 | else 82 | fatal "%s\nHint: consider passing --ignore-missing-files." message 83 | in 84 | let rec search = function 85 | | head::tail -> 86 | let f' = Filename.concat head filename in 87 | if Sys.file_exists f' then 88 | Some f' 89 | else 90 | search tail 91 | | [] -> 92 | fail () 93 | in 94 | if Filename.is_implicit filename then 95 | search source_roots 96 | else if Sys.file_exists filename then 97 | Some filename 98 | else 99 | fail () 100 | 101 | 102 | 103 | let line_counts ~filename ~points ~counts = 104 | let len = Array.length counts in 105 | let points = 106 | points 107 | |> Array.to_list 108 | |> List.mapi (fun index offset -> (offset, index)) 109 | |> List.sort compare 110 | in 111 | let pts = 112 | points |> List.map (fun (offset, index) -> 113 | let nb = 114 | if index < len then 115 | counts.(index) 116 | else 117 | 0 118 | in 119 | (offset, nb)) 120 | in 121 | let in_channel = 122 | try open_in filename 123 | with Sys_error message -> 124 | fatal "cannot open source file '%s': %s" filename message 125 | in 126 | let line_counts = 127 | try 128 | let rec read number acc pts = 129 | match input_line in_channel with 130 | | exception End_of_file -> List.rev acc 131 | | _ -> 132 | let end_ofs = pos_in in_channel in 133 | let before, after = split (fun (o, _) -> o < end_ofs) pts in 134 | let visited_lowest = 135 | List.fold_left (fun v (_, nb) -> 136 | match v with 137 | | None -> Some nb 138 | | Some nb' -> if nb < nb' then Some nb else Some nb') 139 | None 140 | before 141 | in 142 | read (number + 1) (visited_lowest::acc) after 143 | in 144 | read 1 [] pts 145 | with exn -> 146 | close_in_noerr in_channel; 147 | match exn with 148 | | Sys_error message -> 149 | fatal "cannot read source file '%s': %s" filename message 150 | | _ -> 151 | raise exn 152 | in 153 | close_in_noerr in_channel; 154 | line_counts 155 | -------------------------------------------------------------------------------- /src/report/util.mli: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | (** Functions used in several places in the reporter. *) 8 | 9 | 10 | 11 | (** {1 Logging} *) 12 | 13 | val verbose : bool ref 14 | (** Whether {!Util.info} causes any output to be displayed. Set by 15 | [--verbose]. *) 16 | 17 | val info : ('a, unit, string, unit) format4 -> 'a 18 | (** Writes a message to STDERR if {!Util.verbose} is set. The message is 19 | prefixed with ["Info: "]. *) 20 | 21 | val fatal : ('a, unit, string, 'b) format4 -> 'a 22 | (** Writes a message to STDERR and exits the process with [1]. The message is 23 | prefixed with ["Error: "]. 24 | 25 | [bisect-ppx-report] favors this kind of error handling because it is local 26 | and composable. All values needed to generate the error message are 27 | available in local scope, and the "handler" is near the code that causes the 28 | error. On the other hand, [bisect-ppx-report] is a simple enough program 29 | that it does not need to do any fancy error handling (the operating system 30 | will automatically close any open files when the process exits). So there is 31 | no need for a stack of exception handlers to respond to errors. *) 32 | 33 | 34 | 35 | (** {1 General} *) 36 | 37 | val split : ('a -> bool) -> 'a list -> ('a list * 'a list) 38 | (** [split f list] splits [list] into a prefix and suffix. The suffix begins 39 | with the first element of [list] for which [f] evaluated to [false]. *) 40 | 41 | 42 | 43 | (** {1 File system} *) 44 | 45 | val mkdirs : string -> unit 46 | (** Creates the given directory, and any necessary parent directories. Failure 47 | to create directory is considered fatal, and the function terminates the 48 | reporter process. *) 49 | 50 | val find_dune_workspace_root : unit -> string option 51 | (** Returns the directory containing the outermost [dune-workspace] file, 52 | relative to the current directory. *) 53 | 54 | val find_source_file : 55 | source_roots:string list -> ignore_missing_files:bool -> filename:string -> 56 | string option 57 | (** Attempts to find the given file relative to each of the given potential 58 | source roots. If the file cannot be found, either evaluates to [None] if 59 | [~ignore_missing_files:true], or terminates the process if 60 | [~ignore_missing_files:false]. *) 61 | 62 | 63 | 64 | (** {1 Coverage statistics} *) 65 | 66 | val line_counts : 67 | filename:string -> points:int array -> counts:int array -> int option list 68 | (** Computes the visited lines for [~filename]. For each line, returns either: 69 | 70 | - [None], if there is no point on the line. 71 | - [Some count], where [count] is the number of visits to the least-visited 72 | point on the line. The count may be zero. 73 | 74 | This function is "lossy," as OCaml code often has multiple points on one 75 | line. However, this is a necessary conversion for line-based coverage report 76 | formats, such as Coveralls and Cobertura. 77 | 78 | This function reads the file with [~filename]. In case of an error, it 79 | terminates the process. The file's existence should already have been 80 | checked by {!Util.find_source_file}. An error in [line_counts] therefore 81 | suggests a permissions problem, a race condition with another process, or 82 | another abnormal situation. *) 83 | -------------------------------------------------------------------------------- /src/runtime/js/jest.ml: -------------------------------------------------------------------------------- 1 | (* If we make a curried binding of afterAll, BuckleScript will compile it like 2 | this: 3 | 4 | afterAll((function (param) { // <-- notice "param" 5 | // ... snip ... 6 | })); 7 | 8 | this will cause timeout in Jest because Jest will wait until you manually 9 | invoke param.done() within afterAll. *) 10 | external afterAll : ((unit -> unit) [@bs]) -> unit = "afterAll" 11 | [@@bs.val] 12 | 13 | let () = 14 | afterAll 15 | ((fun () -> 16 | Runtime.write_coverage_data (); 17 | Runtime.reset_coverage_data ()) 18 | [@bs]) 19 | -------------------------------------------------------------------------------- /src/runtime/js/runtime.ml: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | (* Code based on Melange, inherited from BuckleScript: 8 | 9 | https://github.com/melange-re/melange/blob/da421be55e755096403425ed3c260486deab61f3/jscomp/others/node_fs.ml *) 10 | module Node = 11 | struct 12 | module Fs = 13 | struct 14 | external openSync : 15 | string -> 16 | ([ `Read [@as "r"] 17 | | `Read_write [@as "r+"] 18 | | `Read_write_sync [@as "rs+"] 19 | | `Write [@as "w"] 20 | | `Write_fail_if_exists [@as "wx"] 21 | | `Write_read [@as "w+"] 22 | | `Write_read_fail_if_exists [@as "wx+"] 23 | | `Append [@as "a"] 24 | | `Append_fail_if_exists [@as "ax"] 25 | | `Append_read [@as "a+"] 26 | | `Append_read_fail_if_exists [@as "ax+"] ] 27 | [@string]) -> 28 | unit = "openSync" 29 | [@@module "fs"] 30 | 31 | type encoding = [ 32 | | `hex 33 | | `utf8 34 | | `ascii 35 | | `latin1 36 | | `base64 37 | | `ucs2 38 | | `base64 39 | | `binary 40 | | `utf16le 41 | ] 42 | 43 | external writeFileSync : string -> string -> encoding -> unit = 44 | "writeFileSync" 45 | [@@val] [@@module "fs"] 46 | end 47 | end 48 | 49 | let get_coverage_data = 50 | Bisect_common.runtime_data_to_string 51 | 52 | let write_coverage_data () = 53 | match get_coverage_data () with 54 | | None -> 55 | () 56 | | Some data -> 57 | let rec create_file attempts = 58 | let filename = Bisect_common.random_filename ~prefix:"bisect" in 59 | match Node.Fs.openSync filename `Write_fail_if_exists with 60 | | exception exn -> 61 | if attempts = 0 then 62 | raise exn 63 | else 64 | create_file (attempts - 1) 65 | | _ -> 66 | Node.Fs.writeFileSync filename data `binary 67 | in 68 | create_file 100 69 | 70 | let reset_coverage_data = 71 | Bisect_common.reset_counters 72 | 73 | let node_at_exit = [%bs.raw {| 74 | function (callback) { 75 | if (typeof process !== 'undefined' && typeof process.on !== 'undefined') 76 | process.on("exit", callback); 77 | } 78 | |}] 79 | 80 | let exit_hook_added = ref false 81 | 82 | let write_coverage_data_on_exit () = 83 | if not !exit_hook_added then begin 84 | node_at_exit (fun () -> write_coverage_data (); reset_coverage_data ()); 85 | exit_hook_added := true 86 | end 87 | 88 | let register_file 89 | ~bisect_file:_ ~bisect_silent:_ ~bisect_sigterm:_ ~filename ~points = 90 | write_coverage_data_on_exit (); 91 | Bisect_common.register_file ~filename ~points 92 | -------------------------------------------------------------------------------- /src/runtime/js/runtime.mli: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | val register_file : 8 | bisect_file:string option -> 9 | bisect_silent:string option -> 10 | bisect_sigterm:bool -> 11 | filename:string -> 12 | points:int array -> 13 | [`Visit of (int -> unit)] 14 | (** [register_file file ~bisect_file ~bisect_silent ~point_count 15 | ~point_definitions] indicates that the file [file] is part of the 16 | application that has been instrumented. [point_definitions] is a serialized 17 | [Common.point_definition list] giving the locations of all points in the 18 | file. The returned callback is used to increment visitation counts. 19 | 20 | [~bisect_file], [~bisect_silent], and [~bisect_sigterm] are ignored. *) 21 | 22 | val get_coverage_data : unit -> string option 23 | (** Returns the binary coverage data accumulated by the program so far. This 24 | should eventually be written to a file, to be processed by 25 | [bisect-ppx-report]. *) 26 | 27 | val write_coverage_data : unit -> unit 28 | (** On Node.js, writes the same coverage data that is returned by 29 | {!get_coverage_data} to a [.coverage] file with a randomized name in the 30 | current directory. *) 31 | 32 | val write_coverage_data_on_exit : unit -> unit 33 | (** Registers {!write_coverage_data} to be called automatically on process 34 | exit. *) 35 | 36 | val reset_coverage_data : unit -> unit 37 | (** [reset_coverage_data ()] clears accumulated coverage statistics. *) 38 | -------------------------------------------------------------------------------- /src/runtime/native/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name bisect) 3 | (public_name bisect_ppx.runtime) 4 | (synopsis "Bisect_ppx runtime library (internal)") 5 | (libraries bisect_ppx.common unix)) 6 | -------------------------------------------------------------------------------- /src/runtime/native/runtime.ml: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | module Common = Bisect_common 8 | 9 | let default_bisect_file = ref "bisect" 10 | 11 | let default_bisect_silent = ref "bisect.log" 12 | 13 | let sigterm_enable = ref false 14 | 15 | let bisect_file_written = ref false 16 | 17 | type message = 18 | | Unable_to_create_file 19 | | Unable_to_write_file 20 | | String of string 21 | 22 | let string_of_message = function 23 | | Unable_to_create_file -> 24 | " *** Bisect runtime was unable to create file." 25 | | Unable_to_write_file -> 26 | " *** Bisect runtime was unable to write file." 27 | | String s -> 28 | " *** " ^ s 29 | 30 | let full_path fname = 31 | if Filename.is_implicit fname then 32 | Filename.concat Filename.current_dir_name fname 33 | else 34 | fname 35 | 36 | let env_to_fname env default = try Sys.getenv env with Not_found -> !default 37 | 38 | let env_to_boolean env default = 39 | try 40 | match String.uppercase_ascii (Sys.getenv env) with 41 | | "YES" -> true 42 | | "NO" -> false 43 | | _ -> default 44 | with Not_found -> default 45 | 46 | let verbose = 47 | lazy begin 48 | let fname = env_to_fname "BISECT_SILENT" default_bisect_silent in 49 | match String.uppercase_ascii fname with 50 | | "YES" | "ON" -> fun _ -> () 51 | | "ERR" -> fun msg -> prerr_endline (string_of_message msg) 52 | | _uc_fname -> 53 | let oc_l = lazy ( 54 | (* A weird race condition is caused if we use this invocation instead 55 | let oc = open_out_gen [Open_append] 0o244 (full_path fname) in 56 | Note that verbose is called only during [at_exit]. *) 57 | let oc = open_out_bin (full_path fname) in 58 | at_exit (fun () -> close_out_noerr oc); 59 | oc) 60 | in 61 | fun msg -> 62 | Printf.fprintf (Lazy.force oc_l) "%s\n" (string_of_message msg) 63 | end 64 | 65 | let verbose message = 66 | (Lazy.force verbose) message 67 | 68 | let get_coverage_data = 69 | Common.runtime_data_to_string 70 | 71 | let write_coverage_data () = 72 | match get_coverage_data () with 73 | | None -> 74 | () 75 | | Some data -> 76 | let rec create_file attempts = 77 | let filename = Common.random_filename ~prefix:"bisect" in 78 | let flags = [Open_wronly; Open_creat; Open_excl; Open_binary] in 79 | match open_out_gen flags 0o644 filename with 80 | | exception exn -> 81 | if attempts = 0 then 82 | raise exn 83 | else 84 | create_file (attempts - 1) 85 | | channel -> 86 | output_string channel data; 87 | close_out_noerr channel 88 | in 89 | create_file 100 90 | 91 | let file_channel () = 92 | let prefix = full_path (env_to_fname "BISECT_FILE" default_bisect_file) in 93 | let rec create_file () = 94 | let filename = Common.random_filename ~prefix in 95 | try 96 | let fd = Unix.(openfile filename [O_WRONLY; O_CREAT; O_EXCL] 0o644) in 97 | let channel = Unix.out_channel_of_descr fd in 98 | Some channel 99 | with 100 | | Unix.Unix_error (Unix.EEXIST, _, _) -> create_file () 101 | | Unix.Unix_error (code, _, _) -> 102 | let detail = Printf.sprintf "%s: %s" (Unix.error_message code) filename in 103 | verbose Unable_to_create_file; 104 | verbose (String detail); 105 | None 106 | in 107 | create_file () 108 | 109 | let reset_counters = 110 | Common.reset_counters 111 | 112 | let dump_counters_exn channel = 113 | Common.coverage 114 | |> Lazy.force 115 | |> Common.write_coverage 116 | |> output_string channel; 117 | flush channel 118 | 119 | let dump () = 120 | match Sys.backend_type with 121 | | Sys.Other "js_of_ocaml" -> 122 | (* The dump function is a no-op when running a js_of_ocaml-compiled binary, 123 | as the Unix file-manipulating functions will not be present; instead, the 124 | user must explicitly call write_coverage_data or get_coverage_data as 125 | appropriate. *) 126 | () 127 | | _ -> 128 | match file_channel () with 129 | | None -> () 130 | | Some channel -> 131 | (try 132 | dump_counters_exn channel 133 | with _ -> 134 | verbose Unable_to_write_file); 135 | close_out_noerr channel 136 | 137 | let sigterm_handler (_ : int) = 138 | bisect_file_written := true; 139 | dump (); 140 | exit 0 141 | 142 | let dump_at_exit () = 143 | if not !bisect_file_written then begin 144 | if !sigterm_enable then begin 145 | ignore @@ Sys.(signal sigterm Signal_ignore); 146 | bisect_file_written := true; 147 | dump (); 148 | ignore @@ Sys.(signal sigterm Signal_default) 149 | end 150 | else 151 | dump () 152 | end 153 | 154 | let register_dump : unit Lazy.t = 155 | lazy (at_exit dump_at_exit) 156 | 157 | let register_sigterm_hander : unit Lazy.t = 158 | lazy (ignore @@ Sys.(signal sigterm (Signal_handle sigterm_handler))) 159 | 160 | let register_file ~bisect_file ~bisect_silent ~bisect_sigterm ~filename ~points = 161 | (match bisect_file with None -> () | Some v -> default_bisect_file := v); 162 | (match bisect_silent with None -> () | Some v -> default_bisect_silent := v); 163 | sigterm_enable := env_to_boolean "BISECT_SIGTERM" bisect_sigterm; 164 | (if !sigterm_enable then Lazy.force register_sigterm_hander); 165 | let () = Lazy.force register_dump in 166 | Common.register_file ~filename ~points 167 | -------------------------------------------------------------------------------- /src/runtime/native/runtime.mli: -------------------------------------------------------------------------------- 1 | (* This file is part of Bisect_ppx, released under the MIT license. See 2 | LICENSE.md for details, or visit 3 | https://github.com/aantron/bisect_ppx/blob/master/LICENSE.md. *) 4 | 5 | 6 | 7 | (** This module provides runtime support for Bisect. Instrumented programs 8 | should hence be linked with this module. 9 | 10 | Each instrumented file creates an array of counters, one for each point in 11 | that file. It then registers the array with this runtime module. Upon 12 | program exit (using [at_exit]), this module dumps the accumulated counts 13 | from all the arrays into an output file. 14 | 15 | The default base name for the output file is [bisect] in the current 16 | directory, but another base name can be specified using the [BISECT_FILE] 17 | environment variable. The actual file name is the first non-existing 18 | [.coverage] file where [base] is the base name and [n] a natural 19 | number value padded with zeroes to 4 digits (i.e. "0001", "0002", and 20 | so on). 21 | 22 | Another environment variable can be used to customize the behaviour of 23 | Bisect: [BISECT_SILENT]. If this variable is set to [YES] or [ON] 24 | (ignoring case), then Bisect will not output any message. Otherwise, Bisect 25 | will output a message in two situations: 26 | - when the output file cannot be created at program termination; 27 | - when the data cannot be written at program termination. 28 | If [BISECT_SILENT] is set to [ERR] (ignoring case), these error messages are 29 | routed to [stderr], otherwise [BISECT_SILENT] is used to determine a 30 | filename for this output. The default value is [bisect.log]. 31 | 32 | If the environment variable [BISECT_SIGTERM] is set to [true], then 33 | the runtime will install a signal handler for [SIGTERM], writing 34 | coverage output before terminating. 35 | 36 | Because instrumented modules refer to [Bisect], one is advised to link 37 | this module as one of the first ones of the program. 38 | 39 | Since the counts output file and log file are, by default, relative to the 40 | current working directory, an instrumented process should be careful about 41 | changing its working directory, or else [BISECT_FILE] and [BISECT_SILENT] 42 | should be specified with absolute paths. *) 43 | 44 | 45 | val register_file : 46 | bisect_file:string option -> 47 | bisect_silent:string option -> 48 | bisect_sigterm:bool -> 49 | filename:string -> 50 | points:int array -> 51 | [`Visit of (int -> unit)] 52 | (** [register_file ~bisect_file ~bisect_silent ~bisect_sigterm file ~point_count 53 | ~point_definitions] indicates that the file [file] is part of the 54 | application that has been instrumented. [point_definitions] is a serialized 55 | [Common.point_definition list] giving the locations of all points in the 56 | file. The returned callback is used to increment visitation counts. 57 | [bisect_file] (resp. [bisect_silent] and [bisect_sigterm]) is a default 58 | value for the environment variable [BISECT_FILE] (resp. [BISECT_SIGTERM] and 59 | [BISECT_SIGTERM]). *) 60 | 61 | val get_coverage_data : unit -> string option 62 | (** Returns the binary coverage data accumulated by the program so far. This 63 | should eventually be written to a file, to be processed by 64 | [bisect-ppx-report]. *) 65 | 66 | val write_coverage_data : unit -> unit 67 | (** On Node.js, writes the same coverage data that is returned by 68 | {!get_coverage_data} to a [.coverage] file with a randomized name in the 69 | current directory. *) 70 | 71 | val dump_counters_exn : out_channel -> unit 72 | (** [dump_counters_exn channel] dumps the runtime coverage counters 73 | to the specified [channel]. 74 | 75 | An exception is raised if writing is not successful *) 76 | 77 | val reset_counters : unit -> unit 78 | (** [reset_counters ()] will reset the runtime coverage counters. *) 79 | -------------------------------------------------------------------------------- /src/vendor/highlight.js/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006, Ivan Sagalaev 2 | All rights reserved. 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of highlight.js nor the names of its contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /src/vendor/highlight.js/README.md: -------------------------------------------------------------------------------- 1 | # Highlight.js 2 | 3 | [![Build Status](https://travis-ci.org/highlightjs/highlight.js.svg?branch=master)](https://travis-ci.org/highlightjs/highlight.js) [![Greenkeeper badge](https://badges.greenkeeper.io/highlightjs/highlight.js.svg)](https://greenkeeper.io/) 4 | 5 | Highlight.js is a syntax highlighter written in JavaScript. It works in 6 | the browser as well as on the server. It works with pretty much any 7 | markup, doesn’t depend on any framework, and has automatic language 8 | detection. 9 | 10 | ## Getting Started 11 | 12 | The bare minimum for using highlight.js on a web page is linking to the 13 | library along with one of the styles and calling 14 | [`initHighlightingOnLoad`][1]: 15 | 16 | ```html 17 | 18 | 19 | 20 | ``` 21 | 22 | This will find and highlight code inside of `
` tags; it tries
 23 | to detect the language automatically. If automatic detection doesn’t
 24 | work for you, you can specify the language in the `class` attribute:
 25 | 
 26 | ```html
 27 | 
...
28 | ``` 29 | 30 | The list of supported language classes is available in the [class 31 | reference][2]. Classes can also be prefixed with either `language-` or 32 | `lang-`. 33 | 34 | To make arbitrary text look like code, but without highlighting, use the 35 | `plaintext` class: 36 | 37 | ```html 38 |
...
39 | ``` 40 | 41 | To disable highlighting altogether use the `nohighlight` class: 42 | 43 | ```html 44 |
...
45 | ``` 46 | 47 | ## Custom Initialization 48 | 49 | When you need a bit more control over the initialization of 50 | highlight.js, you can use the [`highlightBlock`][3] and [`configure`][4] 51 | functions. This allows you to control *what* to highlight and *when*. 52 | 53 | Here’s an equivalent way to calling [`initHighlightingOnLoad`][1] using 54 | vanilla JS: 55 | 56 | ```js 57 | document.addEventListener('DOMContentLoaded', (event) => { 58 | document.querySelectorAll('pre code').forEach((block) => { 59 | hljs.highlightBlock(block); 60 | }); 61 | }); 62 | ``` 63 | 64 | You can use any tags instead of `
` to mark up your code. If
 65 | you don't use a container that preserves line breaks you will need to
 66 | configure highlight.js to use the `
` tag: 67 | 68 | ```js 69 | hljs.configure({useBR: true}); 70 | 71 | document.querySelectorAll('div.code').forEach((block) => { 72 | hljs.highlightBlock(block); 73 | }); 74 | ``` 75 | 76 | For other options refer to the documentation for [`configure`][4]. 77 | 78 | 79 | ## Web Workers 80 | 81 | You can run highlighting inside a web worker to avoid freezing the browser 82 | window while dealing with very big chunks of code. 83 | 84 | In your main script: 85 | 86 | ```js 87 | addEventListener('load', () => { 88 | const code = document.querySelector('#code'); 89 | const worker = new Worker('worker.js'); 90 | worker.onmessage = (event) => { code.innerHTML = event.data; } 91 | worker.postMessage(code.textContent); 92 | }); 93 | ``` 94 | 95 | In worker.js: 96 | 97 | ```js 98 | onmessage = (event) => { 99 | importScripts('/highlight.pack.js'); 100 | const result = self.hljs.highlightAuto(event.data); 101 | postMessage(result.value); 102 | }; 103 | ``` 104 | 105 | 106 | ## Getting the Library 107 | 108 | You can get highlight.js as a hosted, or custom-build, browser script or 109 | as a server module. Right out of the box the browser script supports 110 | both AMD and CommonJS, so if you wish you can use RequireJS or 111 | Browserify without having to build from source. The server module also 112 | works perfectly fine with Browserify, but there is the option to use a 113 | build specific to browsers rather than something meant for a server. 114 | Head over to the [download page][5] for all the options. 115 | 116 | **Don't link to GitHub directly.** The library is not supposed to work straight 117 | from the source, it requires building. If none of the pre-packaged options 118 | work for you refer to the [building documentation][6]. 119 | 120 | **The CDN-hosted package doesn't have all the languages.** Otherwise it'd be 121 | too big. If you don't see the language you need in the ["Common" section][5], 122 | it can be added manually: 123 | 124 | ```html 125 | 128 | ``` 129 | 130 | **On Almond.** You need to use the optimizer to give the module a name. For 131 | example: 132 | 133 | ```bash 134 | r.js -o name=hljs paths.hljs=/path/to/highlight out=highlight.js 135 | ``` 136 | 137 | 138 | ### CommonJS 139 | 140 | You can import Highlight.js as a CommonJS-module: 141 | 142 | ```bash 143 | npm install highlight.js --save 144 | ``` 145 | 146 | In your application: 147 | 148 | ```js 149 | import hljs from 'highlight.js'; 150 | ``` 151 | 152 | The default import imports all languages! Therefore it is likely to be more efficient to import only the library and the languages you need: 153 | 154 | ```js 155 | import hljs from 'highlight.js/lib/highlight'; 156 | import javascript from 'highlight.js/lib/languages/javascript'; 157 | hljs.registerLanguage('javascript', javascript); 158 | ``` 159 | 160 | To set the syntax highlighting style, if your build tool processes CSS from your JavaScript entry point, you can import the stylesheet directly into your CommonJS-module: 161 | 162 | ```js 163 | import hljs from 'highlight.js/lib/highlight'; 164 | import 'highlight.js/styles/github.css'; 165 | ``` 166 | 167 | ## License 168 | 169 | Highlight.js is released under the BSD License. See [LICENSE][7] file 170 | for details. 171 | 172 | ## Links 173 | 174 | The official site for the library is at . 175 | 176 | Further in-depth documentation for the API and other topics is at 177 | . 178 | 179 | Authors and contributors are listed in the [AUTHORS.en.txt][8] file. 180 | 181 | [1]: http://highlightjs.readthedocs.io/en/latest/api.html#inithighlightingonload 182 | [2]: http://highlightjs.readthedocs.io/en/latest/css-classes-reference.html 183 | [3]: http://highlightjs.readthedocs.io/en/latest/api.html#highlightblock-block 184 | [4]: http://highlightjs.readthedocs.io/en/latest/api.html#configure-options 185 | [5]: https://highlightjs.org/download/ 186 | [6]: http://highlightjs.readthedocs.io/en/latest/building-testing.html 187 | [7]: https://github.com/highlightjs/highlight.js/blob/master/LICENSE 188 | [8]: https://github.com/highlightjs/highlight.js/blob/master/AUTHORS.en.txt 189 | -------------------------------------------------------------------------------- /src/vendor/highlight.js/README.ru.md: -------------------------------------------------------------------------------- 1 | # Highlight.js 2 | 3 | Highlight.js — это инструмент для подсветки синтаксиса, написанный на JavaScript. Он работает 4 | и в браузере, и на сервере. Он работает с практически любой HTML разметкой, не 5 | зависит от каких-либо фреймворков и умеет автоматически определять язык. 6 | 7 | 8 | ## Начало работы 9 | 10 | Минимум, что нужно сделать для использования highlight.js на веб-странице — это 11 | подключить библиотеку, CSS-стили и вызывать [`initHighlightingOnLoad`][1]: 12 | 13 | ```html 14 | 15 | 16 | 17 | ``` 18 | 19 | Библиотека найдёт и раскрасит код внутри тегов `
`, попытавшись
 20 | автоматически определить язык. Когда автоопределение не срабатывает, можно явно
 21 | указать язык в атрибуте class:
 22 | 
 23 | ```html
 24 | 
...
25 | ``` 26 | 27 | Список поддерживаемых классов языков доступен в [справочнике по классам][2]. 28 | Класс также можно предварить префиксами `language-` или `lang-`. 29 | 30 | Чтобы отключить подсветку для какого-то блока, используйте класс `nohighlight`: 31 | 32 | ```html 33 |
...
34 | ``` 35 | 36 | ## Инициализация вручную 37 | 38 | Чтобы иметь чуть больше контроля за инициализацией подсветки, вы можете 39 | использовать функции [`highlightBlock`][3] и [`configure`][4]. Таким образом 40 | можно управлять тем, *что* и *когда* подсвечивать. 41 | 42 | Вот пример инициализации, эквивалентной вызову [`initHighlightingOnLoad`][1], но 43 | с использованием `document.addEventListener`: 44 | 45 | ```js 46 | document.addEventListener('DOMContentLoaded', (event) => { 47 | document.querySelectorAll('pre code').forEach((block) => { 48 | hljs.highlightBlock(block); 49 | }); 50 | }); 51 | ``` 52 | 53 | Вы можете использовать любые теги разметки вместо `
`. Если
 54 | используете контейнер, не сохраняющий переводы строк, вам нужно сказать
 55 | highlight.js использовать для них тег `
`: 56 | 57 | ```js 58 | hljs.configure({useBR: true}); 59 | 60 | document.querySelectorAll('div.code').forEach((block) => { 61 | hljs.highlightBlock(block); 62 | }); 63 | ``` 64 | 65 | Другие опции можно найти в документации функции [`configure`][4]. 66 | 67 | 68 | ## Web Workers 69 | 70 | Подсветку можно запустить внутри web worker'а, чтобы окно 71 | браузера не подтормаживало при работе с большими кусками кода. 72 | 73 | В основном скрипте: 74 | 75 | ```js 76 | addEventListener('load', () => { 77 | const code = document.querySelector('#code'); 78 | const worker = new Worker('worker.js'); 79 | worker.onmessage = (event) => { code.innerHTML = event.data; } 80 | worker.postMessage(code.textContent); 81 | }); 82 | ``` 83 | 84 | В worker.js: 85 | 86 | ```js 87 | onmessage = (event) => { 88 | importScripts('/highlight.pack.js'); 89 | const result = self.hljs.highlightAuto(event.data); 90 | postMessage(result.value); 91 | }; 92 | ``` 93 | 94 | 95 | ## Установка библиотеки 96 | 97 | Highlight.js можно использовать в браузере прямо с CDN хостинга или скачать 98 | индивидуальную сборку, а также установив модуль на сервере. На 99 | [странице загрузки][5] подробно описаны все варианты. 100 | 101 | **Не подключайте GitHub напрямую.** Библиотека не предназначена для 102 | использования в виде исходного кода, а требует отдельной сборки. Если вам не 103 | подходит ни один из готовых вариантов, читайте [документацию по сборке][6]. 104 | 105 | **Файл на CDN содержит не все языки.** Иначе он будет слишком большого размера. 106 | Если нужного вам языка нет в [категории "Common"][5], можно дообавить его 107 | вручную: 108 | 109 | ```html 110 | 111 | ``` 112 | 113 | **Про Almond.** Нужно задать имя модуля в оптимизаторе, например: 114 | 115 | ``` 116 | r.js -o name=hljs paths.hljs=/path/to/highlight out=highlight.js 117 | ``` 118 | 119 | 120 | ## Лицензия 121 | 122 | Highlight.js распространяется под лицензией BSD. Подробнее читайте файл 123 | [LICENSE][7]. 124 | 125 | 126 | ## Ссылки 127 | 128 | Официальный сайт билиотеки расположен по адресу . 129 | 130 | Более подробная документация по API и другим темам расположена на 131 | . 132 | 133 | Авторы и контрибьюторы перечислены в файле [AUTHORS.ru.txt][8] file. 134 | 135 | [1]: http://highlightjs.readthedocs.io/en/latest/api.html#inithighlightingonload 136 | [2]: http://highlightjs.readthedocs.io/en/latest/css-classes-reference.html 137 | [3]: http://highlightjs.readthedocs.io/en/latest/api.html#highlightblock-block 138 | [4]: http://highlightjs.readthedocs.io/en/latest/api.html#configure-options 139 | [5]: https://highlightjs.org/download/ 140 | [6]: http://highlightjs.readthedocs.io/en/latest/building-testing.html 141 | [7]: https://github.com/highlightjs/highlight.js/blob/master/LICENSE 142 | [8]: https://github.com/highlightjs/highlight.js/blob/master/AUTHORS.ru.txt 143 | -------------------------------------------------------------------------------- /test/.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Shell 2 | -------------------------------------------------------------------------------- /test/ci/binaries.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | git remote set-url origin git@github.com:aantron/bisect_ppx.git 6 | git config user.name "Anton Bachin" 7 | git config user.email "antonbachin@yahoo.com" 8 | mkdir -p ~/.ssh 9 | chmod 700 ~/.ssh 10 | echo "$DEPLOY_KEY" | base64 --decode > ~/.ssh/binaries 11 | chmod 400 ~/.ssh/binaries 12 | echo >> ~/.ssh/config 13 | echo "Host github.com" >> ~/.ssh/config 14 | echo " IdentityFile ~/.ssh/binaries" >> ~/.ssh/config 15 | echo " StrictHostKeyChecking no" >> ~/.ssh/config 16 | 17 | set +e 18 | set -x 19 | 20 | case $RUNNER_OS in 21 | "Linux") OS=linux;; 22 | "macOS") OS=macos;; 23 | *) echo Unsupported system $RUNNER_OS; exit 1;; 24 | esac 25 | 26 | try_to_commit() { 27 | git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" 28 | git fetch --unshallow origin 29 | git checkout -b binaries origin/binaries || git checkout -b binaries 30 | if ! git merge-base --is-ancestor $GITHUB_SHA binaries 31 | then 32 | git reset --hard $GITHUB_SHA 33 | fi 34 | mkdir -p bin/$OS 35 | cp test/js/node_modules/bisect_ppx/ppx bin/$OS/ 36 | cp test/js/node_modules/.bin/bisect-ppx-report bin/$OS/ 37 | strip bin/$OS/ppx 38 | strip bin/$OS/bisect-ppx-report 39 | git add bin/ 40 | cp src/report/dummy-binary ./bisect-ppx-report 41 | git add bisect-ppx-report 42 | echo "Binaries for '$OS'" > commit-message 43 | if [ `ls bin | wc -l` != 2 ] 44 | then 45 | echo >> commit-message 46 | echo "[skip ci]" >> commit-message 47 | fi 48 | git commit -F commit-message 49 | git push --force-with-lease -u origin binaries 50 | RESULT=$? 51 | git checkout $GITHUB_REF 52 | git branch -D binaries 53 | return $RESULT 54 | } 55 | 56 | if ! try_to_commit 57 | then 58 | if ! try_to_commit 59 | then 60 | try_to_commit 61 | fi 62 | fi 63 | -------------------------------------------------------------------------------- /test/ci/travis-wrapped-esy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$1" == install ] 4 | then 5 | EXTRA=--skip-repository-update 6 | else 7 | EXTRA= 8 | fi 9 | 10 | exec $HOME/build/aantron/bisect_ppx/node_modules/.bin/esy "$@" $EXTRA 11 | -------------------------------------------------------------------------------- /test/dune: -------------------------------------------------------------------------------- 1 | (dirs (:standard \ js)) 2 | -------------------------------------------------------------------------------- /test/instrument/apply/and.t: -------------------------------------------------------------------------------- 1 | In logical AND, control might not reach the second argument, so it is 2 | instrumented. 3 | 4 | $ bash ../test.sh <<'EOF' 5 | > let _ = true && false 6 | > let _ = (true & false) [@ocaml.warning "-3"] 7 | > EOF 8 | let _ = 9 | true 10 | && 11 | (___bisect_visit___ 0; 12 | false) 13 | 14 | let _ = 15 | (true 16 | & 17 | (___bisect_visit___ 1; 18 | false)) 19 | [@ocaml.warning "-3"] 20 | 21 | 22 | Recursive instrumentation of subexpressions. 23 | 24 | $ bash ../test.sh <<'EOF' 25 | > let _ = (bool_of_string "true") && (bool_of_string "false") 26 | > let _ = 27 | > ((bool_of_string "true") & (bool_of_string "false")) [@ocaml.warning "-3"] 28 | > EOF 29 | let _ = 30 | ___bisect_post_visit___ 0 (bool_of_string "true") 31 | && 32 | (___bisect_visit___ 2; 33 | ___bisect_post_visit___ 1 (bool_of_string "false")) 34 | 35 | let _ = 36 | (___bisect_post_visit___ 3 (bool_of_string "true") 37 | & 38 | (___bisect_visit___ 5; 39 | ___bisect_post_visit___ 4 (bool_of_string "false"))) 40 | [@ocaml.warning "-3"] 41 | 42 | 43 | Partial application. See https://github.com/aantron/bisect_ppx/issues/333. 44 | 45 | $ bash ../test.sh <<'EOF' 46 | > [@@@ocaml.warning "-5"] 47 | > let _ = (&&) (List.mem 0 []) 48 | > EOF 49 | [@@@ocaml.warning "-5"] 50 | 51 | let _ = ( && ) (___bisect_post_visit___ 0 (List.mem 0 [])) 52 | 53 | 54 | The second subexpression is not post-instrumented if it is in tail position. 55 | 56 | $ bash ../test.sh <<'EOF' 57 | > let f _ = (bool_of_string "true") && (bool_of_string "false") 58 | > EOF 59 | let f _ = 60 | ___bisect_visit___ 2; 61 | ___bisect_post_visit___ 0 (bool_of_string "true") 62 | && 63 | (___bisect_visit___ 1; 64 | bool_of_string "false") 65 | -------------------------------------------------------------------------------- /test/instrument/apply/apply.t: -------------------------------------------------------------------------------- 1 | Post-instrumented when they are not in tail position. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = print_endline "foo" 5 | > EOF 6 | let _ = ___bisect_post_visit___ 0 (print_endline "foo") 7 | 8 | 9 | Not instrumented when in tail position. 10 | 11 | $ bash ../test.sh <<'EOF' 12 | > let _ = fun () -> print_endline "foo" 13 | > EOF 14 | let _ = 15 | fun () -> 16 | ___bisect_visit___ 0; 17 | print_endline "foo" 18 | 19 | 20 | Arguments instrumented recursively. 21 | 22 | $ bash ../test.sh <<'EOF' 23 | > let _ = String.trim (String.trim "foo") 24 | > EOF 25 | let _ = 26 | ___bisect_post_visit___ 1 27 | (String.trim (___bisect_post_visit___ 0 (String.trim "foo"))) 28 | 29 | 30 | Function position instrumented recursively. 31 | 32 | $ bash ../test.sh <<'EOF' 33 | > let _ = (List.map ignore) [] 34 | > EOF 35 | let _ = 36 | ___bisect_post_visit___ 0 ((___bisect_post_visit___ 0 (List.map ignore)) []) 37 | 38 | 39 | Multiple arguments don't produce nested instrumentation. 40 | 41 | $ bash ../test.sh <<'EOF' 42 | > let _ = List.map ignore [] 43 | > EOF 44 | let _ = ___bisect_post_visit___ 0 (List.map ignore []) 45 | 46 | 47 | Labels preserved. 48 | 49 | $ bash ../test.sh <<'EOF' 50 | > let _ = ListLabels.iter ~f:ignore [] 51 | > EOF 52 | let _ = ___bisect_post_visit___ 0 (ListLabels.iter ~f:ignore []) 53 | 54 | 55 | Instrumentation suppressed if all arguments labeled. 56 | 57 | $ bash ../test.sh <<'EOF' 58 | > [@@@ocaml.warning "-5"] 59 | > let _ = ListLabels.iter ~f:ignore 60 | > EOF 61 | [@@@ocaml.warning "-5"] 62 | 63 | let _ = ListLabels.iter ~f:ignore 64 | -------------------------------------------------------------------------------- /test/instrument/apply/assert.t: -------------------------------------------------------------------------------- 1 | Out-edge instrumented. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = assert (bool_of_string "true") 5 | > EOF 6 | let _ = 7 | ___bisect_post_visit___ 1 8 | (assert (___bisect_post_visit___ 0 (bool_of_string "true"))) 9 | 10 | 11 | Not instrumented for assert false. 12 | 13 | $ bash ../test.sh <<'EOF' 14 | > let _ = assert false 15 | > EOF 16 | let _ = assert false 17 | -------------------------------------------------------------------------------- /test/instrument/apply/dune: -------------------------------------------------------------------------------- 1 | (cram 2 | (deps ../test.sh) 3 | (alias compatible)) 4 | -------------------------------------------------------------------------------- /test/instrument/apply/operator.t: -------------------------------------------------------------------------------- 1 | Instrumentation of partially-applied functions on the left of (@@) is 2 | suppressed. 3 | 4 | $ bash ../test.sh <<'EOF' 5 | > let _ = ListLabels.iter ~f:ignore @@ [] 6 | > EOF 7 | let _ = ___bisect_post_visit___ 0 (ListLabels.iter ~f:ignore @@ []) 8 | 9 | 10 | Subexpressions instrumented recursively. 11 | 12 | $ bash ../test.sh <<'EOF' 13 | > let _ = String.concat (String.trim "") @@ [] 14 | > let _ = (fun () -> ()) @@ () 15 | > let _ = String.concat "" @@ List.append [] [] 16 | > EOF 17 | let _ = 18 | ___bisect_post_visit___ 1 19 | (String.concat (___bisect_post_visit___ 0 (String.trim "")) @@ []) 20 | 21 | let _ = 22 | ___bisect_post_visit___ 3 23 | ((fun () -> 24 | ___bisect_visit___ 2; 25 | ()) 26 | @@ ()) 27 | 28 | let _ = 29 | ___bisect_post_visit___ 5 30 | (String.concat "" @@ ___bisect_post_visit___ 4 (List.append [] [])) 31 | -------------------------------------------------------------------------------- /test/instrument/apply/or.t: -------------------------------------------------------------------------------- 1 | Logical OR is expanded so that the operands can be instrumented individually. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = true || false 5 | > let _ = true or false 6 | > EOF 7 | let _ = 8 | if true then ( 9 | ___bisect_visit___ 0; 10 | true) 11 | else if false then ( 12 | ___bisect_visit___ 1; 13 | true) 14 | else false 15 | 16 | let _ = 17 | if true then ( 18 | ___bisect_visit___ 2; 19 | true) 20 | else if false then ( 21 | ___bisect_visit___ 3; 22 | true) 23 | else false 24 | 25 | 26 | If the right operand is also a logical OR, the instrumentation is "associative" 27 | rather than nested. 28 | 29 | $ bash ../test.sh <<'EOF' 30 | > let _ = true || true || false 31 | > let _ = true or true or false 32 | > EOF 33 | let _ = 34 | if true then ( 35 | ___bisect_visit___ 0; 36 | true) 37 | else if true then ( 38 | ___bisect_visit___ 1; 39 | true) 40 | else if false then ( 41 | ___bisect_visit___ 2; 42 | true) 43 | else false 44 | 45 | let _ = 46 | if true then ( 47 | ___bisect_visit___ 3; 48 | true) 49 | else if true then ( 50 | ___bisect_visit___ 4; 51 | true) 52 | else if false then ( 53 | ___bisect_visit___ 5; 54 | true) 55 | else false 56 | 57 | 58 | Recursive instrumentation of subexpressions. 59 | 60 | $ bash ../test.sh <<'EOF' 61 | > let _ = (bool_of_string "true") || (bool_of_string "false") 62 | > let _ = (bool_of_string "true") or (bool_of_string "false") 63 | > EOF 64 | let _ = 65 | if ___bisect_post_visit___ 3 (bool_of_string "true") then ( 66 | ___bisect_visit___ 0; 67 | true) 68 | else if ___bisect_post_visit___ 1 (bool_of_string "false") then ( 69 | ___bisect_visit___ 2; 70 | true) 71 | else false 72 | 73 | let _ = 74 | if ___bisect_post_visit___ 7 (bool_of_string "true") then ( 75 | ___bisect_visit___ 4; 76 | true) 77 | else if ___bisect_post_visit___ 5 (bool_of_string "false") then ( 78 | ___bisect_visit___ 6; 79 | true) 80 | else false 81 | 82 | 83 | Function calls on the right in tail position remain in tail position. Any 84 | would-be surrounding instrumentation is suppressed. 85 | 86 | $ bash ../test.sh <<'EOF' 87 | > let f _ = (bool_of_string "true") || (bool_of_string "false") 88 | > let g _ = 89 | > (bool_of_string "true") or ((bool_of_string [@ocaml.tailcall]) "false") 90 | > EOF 91 | let f _ = 92 | ___bisect_visit___ 2; 93 | if ___bisect_post_visit___ 1 (bool_of_string "true") then ( 94 | ___bisect_visit___ 0; 95 | true) 96 | else bool_of_string "false" 97 | 98 | let g _ = 99 | ___bisect_visit___ 5; 100 | if ___bisect_post_visit___ 4 (bool_of_string "true") then ( 101 | ___bisect_visit___ 3; 102 | true) 103 | else (bool_of_string [@ocaml.tailcall]) "false" 104 | 105 | 106 | Surrounding instrumentation is still generated when the second function is a 107 | well-known trivial function. 108 | 109 | $ bash ../test.sh <<'EOF' 110 | > let f _ = (bool_of_string "true") || (true <> false) 111 | > EOF 112 | let f _ = 113 | ___bisect_visit___ 3; 114 | if ___bisect_post_visit___ 2 (bool_of_string "true") then ( 115 | ___bisect_visit___ 0; 116 | true) 117 | else if true <> false then ( 118 | ___bisect_visit___ 1; 119 | true) 120 | else false 121 | -------------------------------------------------------------------------------- /test/instrument/apply/pipe.t: -------------------------------------------------------------------------------- 1 | Pipe is given special treatment, to instrument it intuitively as an application 2 | of a function to an argument, rather than a function to two arguments. 3 | 4 | $ bash ../test.sh <<'EOF' 5 | > let _ = "" |> String.trim 6 | > EOF 7 | let _ = ___bisect_post_visit___ 0 ("" |> String.trim) 8 | 9 | 10 | Subexpressions instrumented recursively. 11 | 12 | $ bash ../test.sh <<'EOF' 13 | > let _ = (String.trim "") |> (fun s -> String.trim s) 14 | > EOF 15 | let _ = 16 | ___bisect_post_visit___ 2 17 | ( ___bisect_post_visit___ 0 (String.trim "") |> fun s -> 18 | ___bisect_visit___ 1; 19 | String.trim s ) 20 | 21 | 22 | Instrumentation suppressed in tail position. 23 | 24 | $ bash ../test.sh <<'EOF' 25 | > let _ = fun () -> "" |> String.trim 26 | > EOF 27 | let _ = 28 | fun () -> 29 | ___bisect_visit___ 0; 30 | "" |> String.trim 31 | 32 | 33 | Right argument is not in tail position. 34 | 35 | $ bash ../test.sh <<'EOF' 36 | > let _ = [] |> List.mem 0 37 | > EOF 38 | let _ = ___bisect_post_visit___ 0 ([] |> List.mem 0) 39 | -------------------------------------------------------------------------------- /test/instrument/attribute.t: -------------------------------------------------------------------------------- 1 | Attributes can suppress instrumentation in an expression subtree. 2 | 3 | $ bash test.sh <<'EOF' 4 | > let _ = 5 | > if true then 6 | > ((fun () -> print_endline "foo") [@coverage off]) 7 | > else 8 | > ignore 9 | > EOF 10 | let _ = 11 | if true then fun [@coverage off] () -> print_endline "foo" 12 | else ( 13 | ___bisect_visit___ 0; 14 | ignore) 15 | 16 | 17 | Suppression works even across a transition out of the expression language. 18 | 19 | $ bash test.sh <<'EOF' 20 | > let _ = 21 | > (let module Foo = struct let _bar = fun () -> () end in 22 | > ()) [@coverage off] 23 | > EOF 24 | let _ = 25 | (let module Foo = struct 26 | let _bar () = () 27 | end in 28 | ()) 29 | [@coverage off] 30 | 31 | 32 | Attributes can suppress instrumentation of a structure item. 33 | 34 | $ bash test.sh <<'EOF' 35 | > let f () = () 36 | > [@@coverage off] 37 | > EOF 38 | let f () = () [@@coverage off] 39 | 40 | 41 | Attributes can suppress instrumentation of a range of structure items. 42 | 43 | $ bash test.sh <<'EOF' 44 | > [@@@coverage off] 45 | > let f () = () 46 | > [@@@coverage on] 47 | > let g () = () 48 | > EOF 49 | [@@@coverage off] 50 | 51 | let f () = () 52 | 53 | [@@@coverage on] 54 | 55 | let g () = 56 | ___bisect_visit___ 0; 57 | () 58 | 59 | 60 | Attributes can suppress coverage in a file. 61 | 62 | $ bash test.sh <<'EOF' 63 | > [@@@coverage exclude_file] 64 | > let f () = () 65 | > EOF 66 | 67 | 68 | Non-coverage attributes are preserved uninstrumented. 69 | 70 | $ bash test.sh <<'EOF' 71 | > [@@@foo print_endline "bar"] 72 | > 73 | > let _ = () 74 | > [@@foo print_endline "bar"] 75 | > 76 | > let _ = () [@foo print_endline "bar"] 77 | > EOF 78 | [@@@foo print_endline "bar"] 79 | 80 | let _ = () [@@foo print_endline "bar"] 81 | 82 | let _ = () [@foo print_endline "bar"] 83 | 84 | 85 | Or-pattern coverage is suppressed for cases with [@coverage off]. 86 | 87 | $ bash test.sh <<'EOF' 88 | > let () = 89 | > match `A with 90 | > | `A | `B -> () [@coverage off] 91 | > | `C | `D -> () 92 | > | exception Not_found | exception Exit -> () [@coverage off] 93 | > EOF 94 | let () = 95 | match `A with 96 | | (exception Not_found) | (exception Exit) -> () [@coverage off] 97 | | `A | `B -> () [@coverage off] 98 | | (`C | `D) as ___bisect_matched_value___ -> 99 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 100 | ___bisect_matched_value___ 101 | with 102 | | `C -> 103 | ___bisect_visit___ 0; 104 | () 105 | | `D -> 106 | ___bisect_visit___ 1; 107 | () 108 | | _ -> ()); 109 | () 110 | -------------------------------------------------------------------------------- /test/instrument/class/class.t: -------------------------------------------------------------------------------- 1 | Trivial. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > class foo = 5 | > object 6 | > end 7 | > EOF 8 | class foo = object end 9 | 10 | 11 | Parameters are preserved. 12 | 13 | $ bash ../test.sh <<'EOF' 14 | > class foo_1 () = 15 | > object 16 | > end 17 | > class foo_2 ~l:_ = 18 | > object 19 | > end 20 | > class foo_3 ?l:_ () = 21 | > object 22 | > end 23 | > EOF 24 | class foo_1 () = object end 25 | 26 | class foo_2 ~l:_ = object end 27 | 28 | class foo_3 ?l:_ () = object end 29 | 30 | 31 | Default values are instrumented, and instrumented recursively. 32 | 33 | $ bash ../test.sh <<'EOF' 34 | > [@@@ocaml.warning "-27"] 35 | > class foo ?(l = fun () -> ()) () = 36 | > object 37 | > end 38 | > EOF 39 | [@@@ocaml.warning "-27"] 40 | 41 | class foo 42 | ?(l = 43 | ___bisect_visit___ 1; 44 | fun () -> 45 | ___bisect_visit___ 0; 46 | ()) () = object end 47 | 48 | 49 | Nested expressions and initializers instrumented. 50 | 51 | $ bash ../test.sh <<'EOF' 52 | > class foo = 53 | > let () = print_endline "bar" in 54 | > object 55 | > initializer print_endline "baz" 56 | > end 57 | > EOF 58 | class foo = 59 | let () = ___bisect_post_visit___ 0 (print_endline "bar") in 60 | object 61 | initializer 62 | ___bisect_visit___ 2; 63 | ___bisect_post_visit___ 1 (print_endline "baz") 64 | end 65 | -------------------------------------------------------------------------------- /test/instrument/class/dune: -------------------------------------------------------------------------------- 1 | (cram 2 | (deps ../test.sh) 3 | (alias compatible)) 4 | -------------------------------------------------------------------------------- /test/instrument/class/instvar.t: -------------------------------------------------------------------------------- 1 | Pexp_setinstvar traversed. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = 5 | > object 6 | > val mutable x = () 7 | > method foo = x <- (print_endline "foo") 8 | > end 9 | > EOF 10 | let _ = 11 | object 12 | val mutable x = () 13 | 14 | method foo = 15 | ___bisect_visit___ 1; 16 | x <- ___bisect_post_visit___ 0 (print_endline "foo") 17 | end 18 | 19 | 20 | Pexp_override traversed. 21 | 22 | $ bash ../test.sh <<'EOF' 23 | > let _ = 24 | > object 25 | > val x = () 26 | > method foo = {< x = print_endline "foo" >} 27 | > end 28 | let _ = 29 | object 30 | val x = () 31 | 32 | method foo = 33 | ___bisect_visit___ 1; 34 | {} 35 | end 36 | -------------------------------------------------------------------------------- /test/instrument/class/method.t: -------------------------------------------------------------------------------- 1 | Method "entry point" instrumented. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = 5 | > object 6 | > method foo = () 7 | > end 8 | > EOF 9 | let _ = 10 | object 11 | method foo = 12 | ___bisect_visit___ 0; 13 | () 14 | end 15 | 16 | 17 | Instrumentation is inserted into nested abstractions. 18 | 19 | $ bash ../test.sh <<'EOF' 20 | > let _ = 21 | > object 22 | > method foo () () = () 23 | > method bar = function () -> () 24 | > end 25 | > EOF 26 | let _ = 27 | object 28 | method foo () () = 29 | ___bisect_visit___ 0; 30 | () 31 | 32 | method bar = 33 | function 34 | | () -> 35 | ___bisect_visit___ 1; 36 | () 37 | end 38 | 39 | 40 | Subexpressions instrumented recursively. 41 | 42 | $ bash ../test.sh <<'EOF' 43 | > let _ = 44 | > object 45 | > val foo = print_endline "foo" 46 | > method bar = print_endline "bar" 47 | > end 48 | > EOF 49 | let _ = 50 | object 51 | val foo = ___bisect_post_visit___ 0 (print_endline "foo") 52 | 53 | method bar = 54 | ___bisect_visit___ 1; 55 | print_endline "bar" 56 | end 57 | 58 | 59 | Virtual method preserved. 60 | 61 | $ bash ../test.sh <<'EOF' 62 | > class virtual foo = 63 | > object 64 | > method virtual bar : unit 65 | > end 66 | > EOF 67 | class virtual foo = 68 | object 69 | method virtual bar : unit 70 | end 71 | 72 | 73 | Polymorphic type annotations preserved. 74 | 75 | $ bash ../test.sh <<'EOF' 76 | > let _ = 77 | > object 78 | > method foo : 'a. unit = () 79 | > method bar : 'a. 'a -> unit = fun _ -> () 80 | > end 81 | > EOF 82 | let _ = 83 | object 84 | method foo : 'a. unit = 85 | ___bisect_visit___ 0; 86 | () 87 | 88 | method bar : 'a. 'a -> unit = 89 | fun _ -> 90 | ___bisect_visit___ 1; 91 | () 92 | end 93 | -------------------------------------------------------------------------------- /test/instrument/class/new.t: -------------------------------------------------------------------------------- 1 | New instrumented. 2 | 3 | $ bash ../test.sh << 'EOF' 4 | > class foo = object end 5 | > let _ = new foo 6 | > EOF 7 | class foo = object end 8 | 9 | let _ = ___bisect_post_visit___ 0 (new foo) 10 | 11 | 12 | Not instrumented in tail position. 13 | 14 | $ bash ../test.sh << 'EOF' 15 | > class foo = object end 16 | > let _ = fun () -> new foo 17 | > EOF 18 | class foo = object end 19 | 20 | let _ = 21 | fun () -> 22 | ___bisect_visit___ 0; 23 | new foo 24 | 25 | 26 | Not instrumented inside a surrounding application expression. 27 | 28 | $ bash ../test.sh << 'EOF' 29 | > class foo () = object end 30 | > let _ = new foo () 31 | > EOF 32 | class foo () = object end 33 | 34 | let _ = ___bisect_post_visit___ 0 (new foo ()) 35 | -------------------------------------------------------------------------------- /test/instrument/class/send.t: -------------------------------------------------------------------------------- 1 | Send instrumented. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = (object method foo = () end)#foo 5 | > EOF 6 | let _ = 7 | ___bisect_post_visit___ 1 8 | (object 9 | method foo = 10 | ___bisect_visit___ 0; 11 | () 12 | end) 13 | #foo 14 | 15 | 16 | Not instrumented in tail position. 17 | 18 | $ bash ../test.sh <<'EOF' 19 | > let _ = fun () -> (object method foo = () end)#foo 20 | > EOF 21 | let _ = 22 | fun () -> 23 | ___bisect_visit___ 1; 24 | (object 25 | method foo = 26 | ___bisect_visit___ 0; 27 | () 28 | end) 29 | #foo 30 | 31 | 32 | Not instrumented inside a surrounding application expression. 33 | 34 | $ bash ../test.sh << 'EOF' 35 | > let _ = (object method foo () = () end)#foo () 36 | > EOF 37 | let _ = 38 | ___bisect_post_visit___ 1 39 | ((object 40 | method foo () = 41 | ___bisect_visit___ 0; 42 | () 43 | end) 44 | #foo 45 | ()) 46 | -------------------------------------------------------------------------------- /test/instrument/control/dune: -------------------------------------------------------------------------------- 1 | (cram 2 | (deps ../test.sh) 3 | (alias compatible)) 4 | -------------------------------------------------------------------------------- /test/instrument/control/for.t: -------------------------------------------------------------------------------- 1 | Loop body is instrumented. Condition and bound are not instrumented. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = 5 | > for _index = 0 to 1 do 6 | > () 7 | > done 8 | > EOF 9 | let _ = 10 | for _index = 0 to 1 do 11 | ___bisect_visit___ 0; 12 | () 13 | done 14 | 15 | 16 | Direction is preserved. 17 | 18 | $ bash ../test.sh <<'EOF' 19 | > let _ = 20 | > for _index = 1 downto 0 do 21 | > () 22 | > done 23 | > EOF 24 | let _ = 25 | for _index = 1 downto 0 do 26 | ___bisect_visit___ 0; 27 | () 28 | done 29 | 30 | 31 | Recursive instrumentation of subexpressions. 32 | 33 | $ bash ../test.sh <<'EOF' 34 | > let _ = 35 | > for _index = (for _i = 0 to 1 do () done); 0 36 | > to (for _i = 0 to 1 do () done); 1 37 | > do 38 | > for _i = 0 to 1 do () done 39 | > done 40 | > EOF 41 | let _ = 42 | for 43 | _index = 44 | for _i = 0 to 1 do 45 | ___bisect_visit___ 0; 46 | () 47 | done; 48 | 0 49 | to for _i = 0 to 1 do 50 | ___bisect_visit___ 1; 51 | () 52 | done; 53 | 1 54 | do 55 | ___bisect_visit___ 3; 56 | for _i = 0 to 1 do 57 | ___bisect_visit___ 2; 58 | () 59 | done 60 | done 61 | 62 | 63 | Subexpressions not in tail position. 64 | 65 | $ bash ../test.sh <<'EOF' 66 | > let _ = 67 | > for _index = int_of_string "0" to int_of_string "1" do 68 | > print_endline "foo" 69 | > done 70 | > EOF 71 | let _ = 72 | for 73 | _index = ___bisect_post_visit___ 0 (int_of_string "0") 74 | to ___bisect_post_visit___ 1 (int_of_string "1") 75 | do 76 | ___bisect_visit___ 3; 77 | ___bisect_post_visit___ 2 (print_endline "foo") 78 | done 79 | -------------------------------------------------------------------------------- /test/instrument/control/fun.t: -------------------------------------------------------------------------------- 1 | Instrumentation of internal entry point. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = fun () -> () 5 | > EOF 6 | let _ = 7 | fun () -> 8 | ___bisect_visit___ 0; 9 | () 10 | 11 | 12 | Preservation of labeled arguments and their patterns. 13 | 14 | $ bash ../test.sh <<'EOF' 15 | > let _ = fun ~l:_ -> () 16 | > EOF 17 | let _ = 18 | fun ~l:_ -> 19 | ___bisect_visit___ 0; 20 | () 21 | 22 | 23 | Preservation of optional labeled arguments. 24 | 25 | $ bash ../test.sh <<'EOF' 26 | > let _ = (fun ?l:_ -> ()) [@ocaml.warning "-16"] 27 | > EOF 28 | let _ = 29 | fun [@ocaml.warning "-16"] ?l:_ -> 30 | ___bisect_visit___ 0; 31 | () 32 | 33 | 34 | Preservation of default values. Instrumentation of entry into default values. 35 | Recursive instrumentation of default values. 36 | 37 | $ bash ../test.sh <<'EOF' 38 | > let _ = fun ?(l = fun () -> ()) -> l 39 | > EOF 40 | let _ = 41 | fun ?(l = 42 | ___bisect_visit___ 1; 43 | fun () -> 44 | ___bisect_visit___ 0; 45 | ()) -> 46 | ___bisect_visit___ 2; 47 | l 48 | 49 | 50 | Recursive instrumentation of main subexpression. Instrumentation suppressed on 51 | "between arguments." 52 | 53 | $ bash ../test.sh <<'EOF' 54 | > let _ = fun () -> fun () -> () 55 | > EOF 56 | let _ = 57 | fun () () -> 58 | ___bisect_visit___ 0; 59 | () 60 | 61 | 62 | Instrumentation placed correctly if immediate child is a "return type" 63 | constraint. 64 | 65 | $ bash ../test.sh <<'EOF' 66 | > let _ = fun () -> (() : unit) 67 | > EOF 68 | let _ = 69 | fun () : unit -> 70 | ___bisect_visit___ 0; 71 | () 72 | 73 | 74 | Gentle handling of optional argument elimination. See 75 | https://github.com/aantron/bisect_ppx/issues/319. 76 | 77 | $ bash ../test.sh <<'EOF' 78 | > let f () ?x () = 79 | > x 80 | > 81 | > let () = 82 | > ignore (List.map (f ()) []) 83 | > EOF 84 | let f () ?x () = 85 | ___bisect_visit___ 0; 86 | x 87 | 88 | let () = 89 | ignore 90 | (___bisect_post_visit___ 2 (List.map (___bisect_post_visit___ 1 (f ())) [])) 91 | 92 | 93 | Expressions in default value are not in tail position; expressions in main 94 | subexpression are. 95 | 96 | $ bash ../test.sh <<'EOF' 97 | > [@@@ocaml.warning "-27"] 98 | > let _ = 99 | > fun ?(l = print_endline "foo") () -> print_endline "bar" 100 | > EOF 101 | [@@@ocaml.warning "-27"] 102 | 103 | let _ = 104 | fun ?(l = 105 | ___bisect_visit___ 1; 106 | ___bisect_post_visit___ 0 (print_endline "foo")) () -> 107 | ___bisect_visit___ 2; 108 | print_endline "bar" 109 | -------------------------------------------------------------------------------- /test/instrument/control/function.t: -------------------------------------------------------------------------------- 1 | Instrumentation of cases. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = 5 | > function 6 | > | 0 -> () 7 | > | _ -> () 8 | > EOF 9 | let _ = function 10 | | 0 -> 11 | ___bisect_visit___ 0; 12 | () 13 | | _ -> 14 | ___bisect_visit___ 1; 15 | () 16 | 17 | 18 | Recursive instrumentation of cases. 19 | 20 | $ bash ../test.sh <<'EOF' 21 | > let _ = function () -> function () -> () 22 | > EOF 23 | let _ = function 24 | | () -> ( 25 | ___bisect_visit___ 1; 26 | function 27 | | () -> 28 | ___bisect_visit___ 0; 29 | ()) 30 | 31 | 32 | Instrumentation suppressed "between arguments." 33 | 34 | $ bash ../test.sh <<'EOF' 35 | > let _ = fun () -> function () -> () 36 | > EOF 37 | let _ = 38 | fun () -> function 39 | | () -> 40 | ___bisect_visit___ 0; 41 | () 42 | 43 | 44 | Expressions in cases are in tail position. 45 | 46 | $ bash ../test.sh <<'EOF' 47 | > let _ = function () -> print_endline "foo" 48 | > EOF 49 | let _ = function 50 | | () -> 51 | ___bisect_visit___ 0; 52 | print_endline "foo" 53 | 54 | 55 | Or-pattern. 56 | 57 | $ bash ../test.sh <<'EOF' 58 | > let _ = function None | Some _ -> print_endline "foo" 59 | > EOF 60 | let _ = 61 | fun ___bisect_matched_value___ -> 62 | match ___bisect_matched_value___ with 63 | | None | Some _ -> 64 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 65 | ___bisect_matched_value___ 66 | with 67 | | None -> 68 | ___bisect_visit___ 0; 69 | () 70 | | Some _ -> 71 | ___bisect_visit___ 1; 72 | () 73 | | _ -> ()); 74 | print_endline "foo" 75 | 76 | 77 | Or-pattern with polymorphic variants. 78 | 79 | $ bash ../test.sh <<'EOF' 80 | > let _ = function `A | `B -> print_endline "foo" 81 | > EOF 82 | let _ = function 83 | | (`A | `B) as ___bisect_matched_value___ -> 84 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 85 | ___bisect_matched_value___ 86 | with 87 | | `A -> 88 | ___bisect_visit___ 0; 89 | () 90 | | `B -> 91 | ___bisect_visit___ 1; 92 | () 93 | | _ -> ()); 94 | print_endline "foo" 95 | -------------------------------------------------------------------------------- /test/instrument/control/if.t: -------------------------------------------------------------------------------- 1 | Instrumentation of branches. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = if true then 1 else 2 5 | > EOF 6 | let _ = 7 | if true then ( 8 | ___bisect_visit___ 1; 9 | 1) 10 | else ( 11 | ___bisect_visit___ 0; 12 | 2) 13 | 14 | 15 | Recursive instrumentation of subexpressions. 16 | 17 | $ bash ../test.sh <<'EOF' 18 | > let _ = 19 | > if if true then true else false then 20 | > if true then true else false 21 | > else 22 | > if true then true else false 23 | > EOF 24 | let _ = 25 | if 26 | if true then ( 27 | ___bisect_visit___ 1; 28 | true) 29 | else ( 30 | ___bisect_visit___ 0; 31 | false) 32 | then ( 33 | ___bisect_visit___ 7; 34 | if true then ( 35 | ___bisect_visit___ 3; 36 | true) 37 | else ( 38 | ___bisect_visit___ 2; 39 | false)) 40 | else ( 41 | ___bisect_visit___ 6; 42 | if true then ( 43 | ___bisect_visit___ 5; 44 | true) 45 | else ( 46 | ___bisect_visit___ 4; 47 | false)) 48 | 49 | 50 | Supports if-then. 51 | 52 | $ bash ../test.sh <<'EOF' 53 | > let _ = if true then () 54 | > EOF 55 | let _ = 56 | if true then ( 57 | ___bisect_visit___ 0; 58 | ()) 59 | 60 | 61 | The next expression after if-then is instrumented as if it were an else-case. 62 | 63 | $ bash ../test.sh <<'EOF' 64 | > let _ = (if true then ()); () 65 | > EOF 66 | let _ = 67 | if true then ( 68 | ___bisect_visit___ 1; 69 | ()); 70 | ___bisect_visit___ 0; 71 | () 72 | 73 | 74 | Condition does not need its out-edge instrumented. Expressions in cases are in 75 | tail position iff the whole if-expression is in tail position. 76 | 77 | $ bash ../test.sh <<'EOF' 78 | > let _ = 79 | > if bool_of_string "true" then print_endline "foo" else print_endline "bar" 80 | > let _ = fun () -> 81 | > if bool_of_string "true" then print_endline "foo" else print_endline "bar" 82 | > EOF 83 | let _ = 84 | if bool_of_string "true" then ( 85 | ___bisect_visit___ 3; 86 | ___bisect_post_visit___ 0 (print_endline "foo")) 87 | else ( 88 | ___bisect_visit___ 2; 89 | ___bisect_post_visit___ 1 (print_endline "bar")) 90 | 91 | let _ = 92 | fun () -> 93 | ___bisect_visit___ 6; 94 | if bool_of_string "true" then ( 95 | ___bisect_visit___ 5; 96 | print_endline "foo") 97 | else ( 98 | ___bisect_visit___ 4; 99 | print_endline "bar") 100 | -------------------------------------------------------------------------------- /test/instrument/control/lazy.t: -------------------------------------------------------------------------------- 1 | Thunk body is instrumented. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = lazy (1 + 1) 5 | > EOF 6 | let _ = 7 | lazy 8 | (___bisect_visit___ 0; 9 | 1 + 1) 10 | 11 | 12 | Recursive instrumentation of subexpression. 13 | 14 | $ bash ../test.sh <<'EOF' 15 | > let _ = lazy (lazy (1 + 1)) 16 | > EOF 17 | let _ = 18 | lazy 19 | (___bisect_visit___ 1; 20 | lazy 21 | (___bisect_visit___ 0; 22 | 1 + 1)) 23 | 24 | 25 | Subexpression in tail position. 26 | 27 | $ bash ../test.sh <<'EOF' 28 | > let _ = lazy (print_endline "foo") 29 | > EOF 30 | let _ = 31 | lazy 32 | (___bisect_visit___ 0; 33 | print_endline "foo") 34 | 35 | 36 | Trivial syntactic values are not instrumented. 37 | 38 | $ bash ../test.sh <<'EOF' 39 | > let _ = lazy () 40 | > let _ = lazy false 41 | > let _ = lazy 0 42 | > let _ = lazy 0. 43 | > let _ = lazy "" 44 | > let _ = lazy '0' 45 | > let _ = lazy [] 46 | > let _ = lazy None 47 | > let _ = lazy Exit 48 | > let _ = lazy (fun () -> ()) 49 | > let _ = lazy (function () -> ()) 50 | > let _ = lazy (() : unit) 51 | > let _ = lazy (() :> unit) 52 | > let x = 1 + 1 53 | > let _ = lazy x 54 | > EOF 55 | let _ = lazy () 56 | 57 | let _ = lazy false 58 | 59 | let _ = lazy 0 60 | 61 | let _ = lazy 0. 62 | 63 | let _ = lazy "" 64 | 65 | let _ = lazy '0' 66 | 67 | let _ = lazy [] 68 | 69 | let _ = lazy None 70 | 71 | let _ = lazy Exit 72 | 73 | let _ = 74 | lazy 75 | (fun () -> 76 | ___bisect_visit___ 0; 77 | ()) 78 | 79 | let _ = 80 | lazy 81 | (function 82 | | () -> 83 | ___bisect_visit___ 1; 84 | ()) 85 | 86 | let _ = lazy (() : unit) 87 | 88 | let _ = lazy (() :> unit) 89 | 90 | let x = 1 + 1 91 | 92 | let _ = lazy x 93 | -------------------------------------------------------------------------------- /test/instrument/control/match.t: -------------------------------------------------------------------------------- 1 | Instrumentation of cases. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = 5 | > match true with 6 | > | true -> () 7 | > | false -> () 8 | > EOF 9 | let _ = 10 | match true with 11 | | true -> 12 | ___bisect_visit___ 0; 13 | () 14 | | false -> 15 | ___bisect_visit___ 1; 16 | () 17 | 18 | 19 | Recursive instrumentation of cases. 20 | 21 | $ bash ../test.sh <<'EOF' 22 | > let _ = 23 | > match 24 | > match () with 25 | > | () -> () 26 | > with 27 | > | () -> 28 | > match () with 29 | > | () -> () 30 | > EOF 31 | let _ = 32 | match 33 | match () with 34 | | () -> 35 | ___bisect_visit___ 2; 36 | () 37 | with 38 | | () -> ( 39 | ___bisect_visit___ 1; 40 | match () with 41 | | () -> 42 | ___bisect_visit___ 0; 43 | ()) 44 | 45 | 46 | Expressions in selector don't need their out-edge instrumented. Expressions in 47 | cases are in tail position iff the match expression is in tail position. 48 | 49 | $ bash ../test.sh <<'EOF' 50 | > let _ = 51 | > match print_endline "foo" with () -> print_endline "bar" 52 | > let _ = fun () -> 53 | > match print_endline "foo" with () -> print_endline "bar" 54 | > EOF 55 | let _ = 56 | match print_endline "foo" with 57 | | () -> 58 | ___bisect_visit___ 1; 59 | ___bisect_post_visit___ 0 (print_endline "bar") 60 | 61 | let _ = 62 | fun () -> 63 | ___bisect_visit___ 3; 64 | match print_endline "foo" with 65 | | () -> 66 | ___bisect_visit___ 2; 67 | print_endline "bar" 68 | -------------------------------------------------------------------------------- /test/instrument/control/newtype.t: -------------------------------------------------------------------------------- 1 | Pseudo-entry point of newtype is not instrumented. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = fun (type _t) -> () 5 | > EOF 6 | let _ = fun (type _t) -> () 7 | 8 | 9 | Recursive instrumentation of subexpression. 10 | 11 | $ bash ../test.sh <<'EOF' 12 | > let _ = fun (type _t) -> fun x -> x 13 | > EOF 14 | let _ = 15 | fun (type _t) x -> 16 | ___bisect_visit___ 0; 17 | x 18 | 19 | 20 | Subexpression in tail position iff whole expression is in tail position. 21 | 22 | $ bash ../test.sh <<'EOF' 23 | > let _ = 24 | > fun (type _t) -> print_endline "foo" 25 | > let _ = fun () -> 26 | > fun (type _t) -> print_endline "foo" 27 | > EOF 28 | let _ = fun (type _t) -> ___bisect_post_visit___ 0 (print_endline "foo") 29 | 30 | let _ = 31 | fun () -> 32 | ___bisect_visit___ 1; 33 | fun (type _t) -> print_endline "foo" 34 | -------------------------------------------------------------------------------- /test/instrument/control/try.t: -------------------------------------------------------------------------------- 1 | Instrumentation of cases. No instrumentation of main subexpression. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = 5 | > try () 6 | > with 7 | > | Exit -> () 8 | > | Failure _ -> () 9 | > EOF 10 | let _ = 11 | try () with 12 | | Exit -> 13 | ___bisect_visit___ 0; 14 | () 15 | | Failure _ -> 16 | ___bisect_visit___ 1; 17 | () 18 | 19 | 20 | Recursive instrumentation of subexpressions. 21 | 22 | $ bash ../test.sh <<'EOF' 23 | > let _ = 24 | > try 25 | > try () with _ -> () 26 | > with _ -> 27 | > try () with _ -> () 28 | > EOF 29 | let _ = 30 | try 31 | try () 32 | with _ -> 33 | ___bisect_visit___ 2; 34 | () 35 | with _ -> ( 36 | ___bisect_visit___ 1; 37 | try () 38 | with _ -> 39 | ___bisect_visit___ 0; 40 | ()) 41 | 42 | 43 | Main subexpression is not in tail position. Handler is in tail position iff the 44 | whole expression is in tail position. 45 | 46 | $ bash ../test.sh <<'EOF' 47 | > let _ = 48 | > try print_endline "foo" with _ -> print_endline "bar" 49 | > let _ = fun () -> 50 | > try print_endline "foo" with _ -> print_endline "bar" 51 | > EOF 52 | let _ = 53 | try ___bisect_post_visit___ 2 (print_endline "foo") 54 | with _ -> 55 | ___bisect_visit___ 1; 56 | ___bisect_post_visit___ 0 (print_endline "bar") 57 | 58 | let _ = 59 | fun () -> 60 | ___bisect_visit___ 5; 61 | try ___bisect_post_visit___ 4 (print_endline "foo") 62 | with _ -> 63 | ___bisect_visit___ 3; 64 | print_endline "bar" 65 | 66 | 67 | Or-pattern. 68 | 69 | $ bash ../test.sh <<'EOF' 70 | > let _ = 71 | > try () 72 | > with Exit | End_of_file -> () 73 | > EOF 74 | let _ = 75 | try () 76 | with (Exit | End_of_file) as ___bisect_matched_value___ -> 77 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 78 | ___bisect_matched_value___ 79 | with 80 | | Exit -> 81 | ___bisect_visit___ 0; 82 | () 83 | | End_of_file -> 84 | ___bisect_visit___ 1; 85 | () 86 | | _ -> ()); 87 | () 88 | -------------------------------------------------------------------------------- /test/instrument/control/while.t: -------------------------------------------------------------------------------- 1 | Loop body is instrumented. Condition is not instrumented. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = while true do () done 5 | > EOF 6 | let _ = 7 | while true do 8 | ___bisect_visit___ 0; 9 | () 10 | done 11 | 12 | 13 | Recursive instrumentation of subexpressions. 14 | 15 | $ bash ../test.sh <<'EOF' 16 | > let _ = 17 | > while 18 | > (while true do () done); true 19 | > do 20 | > while true do () done 21 | > done 22 | > EOF 23 | let _ = 24 | while 25 | while true do 26 | ___bisect_visit___ 0; 27 | () 28 | done; 29 | true 30 | do 31 | ___bisect_visit___ 2; 32 | while true do 33 | ___bisect_visit___ 1; 34 | () 35 | done 36 | done 37 | 38 | 39 | Subexpressions not in tail position. 40 | 41 | $ bash ../test.sh <<'EOF' 42 | > let _ = while bool_of_string "true" do print_endline "foo" done 43 | > EOF 44 | let _ = 45 | while ___bisect_post_visit___ 0 (bool_of_string "true") do 46 | ___bisect_visit___ 2; 47 | ___bisect_post_visit___ 1 (print_endline "foo") 48 | done 49 | -------------------------------------------------------------------------------- /test/instrument/dune: -------------------------------------------------------------------------------- 1 | (cram 2 | (deps test.sh) 3 | (alias compatible)) 4 | -------------------------------------------------------------------------------- /test/instrument/mangle.t: -------------------------------------------------------------------------------- 1 | If Bisect does not assign unique names to the internal instrumentation modules 2 | it generates, including one module from another will trigger an error. 3 | 4 | $ echo "(lang dune 2.7)" > dune-project 5 | $ cat > dune <<'EOF' 6 | > (executable 7 | > (name test) 8 | > (modes byte) 9 | > (instrumentation (backend bisect_ppx))) 10 | > EOF 11 | $ cat > test.ml <<'EOF' 12 | > include Helper 13 | > let () = () 14 | > EOF 15 | $ cat > helper.ml <<'EOF' 16 | > let () = () 17 | > EOF 18 | $ dune build --instrument-with bisect_ppx --display quiet 19 | -------------------------------------------------------------------------------- /test/instrument/mli.t: -------------------------------------------------------------------------------- 1 | .mli files are not instrumented. 2 | 3 | $ echo > .ocamlformat 4 | $ echo "(lang dune 2.7)" > dune-project 5 | $ cat > dune <<'EOF' 6 | > (executable 7 | > (name test) 8 | > (modes byte) 9 | > (ocamlc_flags -dsource) 10 | > (instrumentation (backend bisect_ppx))) 11 | > EOF 12 | $ cat > test.ml <<'EOF' 13 | > let f () = () 14 | > EOF 15 | $ cat > test.mli <<'EOF' 16 | > val f : unit -> unit 17 | > EOF 18 | $ cat > sanitize.sh <<'EOF' 19 | > while read line 20 | > do 21 | > echo "$line" 22 | > ! [[ "$line" =~ val ]] || exit 0 23 | > done 24 | > EOF 25 | $ dune build --instrument-with bisect_ppx 2>&1 | grep -v ocamlc | grep -v '^ [^ =][^ =]* =\|^ {\|^ }]\|@@@ocaml.ppx' | bash sanitize.sh | ocamlformat --name test.mli - 26 | val f : unit -> unit 27 | 28 | -------------------------------------------------------------------------------- /test/instrument/pattern/binding.t: -------------------------------------------------------------------------------- 1 | Bindings made under or-patterns remain consistent after instrumentation. 2 | 3 | $ (bash ../test.sh | tail -n +4) <<'EOF' 4 | > let _ = 5 | > match `A with 6 | > | (`A as x) | (`B as x) -> print_endline "foo"; x 7 | > EOF 8 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 9 | ___bisect_matched_value___ 10 | with 11 | | `A as x -> 12 | ___bisect_visit___ 1; 13 | () 14 | | `B as x -> 15 | ___bisect_visit___ 2; 16 | () 17 | | _ -> ()); 18 | ___bisect_post_visit___ 0 (print_endline "foo"); 19 | x 20 | 21 | $ bash ../test.sh <<'EOF' 22 | > let _ = 23 | > match `A () with 24 | > | `A x | `B x -> print_endline "foo"; x 25 | > EOF 26 | let _ = 27 | match `A () with 28 | | (`A x | `B x) as ___bisect_matched_value___ -> 29 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 30 | ___bisect_matched_value___ 31 | with 32 | | `A x -> 33 | ___bisect_visit___ 1; 34 | () 35 | | `B x -> 36 | ___bisect_visit___ 2; 37 | () 38 | | _ -> ()); 39 | ___bisect_post_visit___ 0 (print_endline "foo"); 40 | x 41 | -------------------------------------------------------------------------------- /test/instrument/pattern/dune: -------------------------------------------------------------------------------- 1 | (cram 2 | (deps ../test.sh) 3 | (alias compatible)) 4 | -------------------------------------------------------------------------------- /test/instrument/pattern/exception.t: -------------------------------------------------------------------------------- 1 | Exception or-patterns. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = 5 | > match () with 6 | > | () -> () 7 | > | exception (Exit | Failure _) -> () 8 | > EOF 9 | let _ = 10 | match () with 11 | | exception ((Exit | Failure _) as ___bisect_matched_value___) -> 12 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 13 | ___bisect_matched_value___ 14 | with 15 | | Exit -> 16 | ___bisect_visit___ 1; 17 | () 18 | | Failure _ -> 19 | ___bisect_visit___ 2; 20 | () 21 | | _ -> ()); 22 | () 23 | | () -> 24 | ___bisect_visit___ 0; 25 | () 26 | 27 | 28 | Mixed value-exception cases are partitioned. Order is preserved. Case are 29 | factored out into functions, whose parameters are the bound variables of the 30 | patterns. 31 | 32 | $ bash ../test.sh <<'EOF' 33 | > let _ = 34 | > match Exit with 35 | > | Exit as x | exception (Exit as x) -> ignore x; print_endline "foo" 36 | > | End_of_file as y | exception (End_of_file | Failure _ as y) -> 37 | > ignore y; print_endline "bar" 38 | > | _ -> print_endline "baz" 39 | > EOF 40 | let _ = 41 | let ___bisect_case_0___ x () = 42 | ignore x; 43 | ___bisect_post_visit___ 0 (print_endline "foo") 44 | and ___bisect_case_1___ y () = 45 | ignore y; 46 | ___bisect_post_visit___ 1 (print_endline "bar") 47 | in 48 | match Exit with 49 | | exception (Exit as x) -> 50 | ___bisect_visit___ 4; 51 | ___bisect_case_0___ x () 52 | | exception ((End_of_file | Failure _) as y as ___bisect_matched_value___) -> 53 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 54 | ___bisect_matched_value___ 55 | with 56 | | End_of_file as y -> 57 | ___bisect_visit___ 6; 58 | () 59 | | Failure _ as y -> 60 | ___bisect_visit___ 7; 61 | () 62 | | _ -> ()); 63 | ___bisect_case_1___ y () 64 | | Exit as x -> 65 | ___bisect_visit___ 3; 66 | ___bisect_case_0___ x () 67 | | End_of_file as y -> 68 | ___bisect_visit___ 5; 69 | ___bisect_case_1___ y () 70 | | _ -> 71 | ___bisect_visit___ 8; 72 | ___bisect_post_visit___ 2 (print_endline "baz") 73 | -------------------------------------------------------------------------------- /test/instrument/pattern/nary.t: -------------------------------------------------------------------------------- 1 | Tuple. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = 5 | > match (`A, `C) with 6 | > | ((`A | `B), (`C | `D)) -> print_endline "foo" 7 | > EOF 8 | let _ = 9 | match (`A, `C) with 10 | | ((`A | `B), (`C | `D)) as ___bisect_matched_value___ -> 11 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 12 | ___bisect_matched_value___ 13 | with 14 | | `A, `C -> 15 | ___bisect_visit___ 2; 16 | ___bisect_visit___ 1; 17 | () 18 | | `A, `D -> 19 | ___bisect_visit___ 3; 20 | ___bisect_visit___ 1; 21 | () 22 | | `B, `C -> 23 | ___bisect_visit___ 2; 24 | ___bisect_visit___ 4; 25 | () 26 | | `B, `D -> 27 | ___bisect_visit___ 3; 28 | ___bisect_visit___ 4; 29 | () 30 | | _ -> ()); 31 | ___bisect_post_visit___ 0 (print_endline "foo") 32 | 33 | 34 | Record. 35 | 36 | $ bash ../test.sh <<'EOF' 37 | > type t = {a : bool; b : bool} 38 | > let _ = 39 | > match {a = true; b = false} with 40 | > | {a = true | false; b = true | false} -> print_endline "foo" 41 | > EOF 42 | type t = { a : bool; b : bool } 43 | 44 | let _ = 45 | match { a = true; b = false } with 46 | | ___bisect_matched_value___ -> ( 47 | match ___bisect_matched_value___ with 48 | | { a = true | false; b = true | false } -> 49 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 50 | ___bisect_matched_value___ 51 | with 52 | | { a = true; b = true } -> 53 | ___bisect_visit___ 2; 54 | ___bisect_visit___ 1; 55 | () 56 | | { a = true; b = false } -> 57 | ___bisect_visit___ 3; 58 | ___bisect_visit___ 1; 59 | () 60 | | { a = false; b = true } -> 61 | ___bisect_visit___ 2; 62 | ___bisect_visit___ 4; 63 | () 64 | | { a = false; b = false } -> 65 | ___bisect_visit___ 3; 66 | ___bisect_visit___ 4; 67 | () 68 | | _ -> ()); 69 | ___bisect_post_visit___ 0 (print_endline "foo")) 70 | 71 | 72 | Array. 73 | 74 | $ bash ../test.sh <<'EOF' 75 | > let _ = 76 | > match [|`A; `C|] with 77 | > | [|`A | `B; `C | `D|] -> print_endline "foo" 78 | > | _ -> () 79 | > EOF 80 | let _ = 81 | match [| `A; `C |] with 82 | | [| `A | `B; `C | `D |] as ___bisect_matched_value___ -> 83 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 84 | ___bisect_matched_value___ 85 | with 86 | | [| `A; `C |] -> 87 | ___bisect_visit___ 2; 88 | ___bisect_visit___ 1; 89 | () 90 | | [| `A; `D |] -> 91 | ___bisect_visit___ 3; 92 | ___bisect_visit___ 1; 93 | () 94 | | [| `B; `C |] -> 95 | ___bisect_visit___ 2; 96 | ___bisect_visit___ 4; 97 | () 98 | | [| `B; `D |] -> 99 | ___bisect_visit___ 3; 100 | ___bisect_visit___ 4; 101 | () 102 | | _ -> ()); 103 | ___bisect_post_visit___ 0 (print_endline "foo") 104 | | _ -> 105 | ___bisect_visit___ 5; 106 | () 107 | -------------------------------------------------------------------------------- /test/instrument/pattern/nullary.t: -------------------------------------------------------------------------------- 1 | Wildcard. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = 5 | > match () with 6 | > | _ -> () 7 | > EOF 8 | let _ = 9 | match () with 10 | | _ -> 11 | ___bisect_visit___ 0; 12 | () 13 | 14 | 15 | Variable. 16 | 17 | $ bash ../test.sh <<'EOF' 18 | > let _ = 19 | > match () with 20 | > | x -> x 21 | > EOF 22 | let _ = 23 | match () with 24 | | x -> 25 | ___bisect_visit___ 0; 26 | x 27 | 28 | 29 | Nullary constructor. 30 | 31 | $ bash ../test.sh <<'EOF' 32 | > let _ = 33 | > match () with 34 | > | () -> () 35 | > EOF 36 | let _ = 37 | match () with 38 | | () -> 39 | ___bisect_visit___ 0; 40 | () 41 | 42 | 43 | Constant. 44 | 45 | $ bash ../test.sh <<'EOF' 46 | > let _ = 47 | > match 0 with 48 | > | 0 -> () 49 | > | _ -> () 50 | > EOF 51 | let _ = 52 | match 0 with 53 | | 0 -> 54 | ___bisect_visit___ 0; 55 | () 56 | | _ -> 57 | ___bisect_visit___ 1; 58 | () 59 | 60 | 61 | Interval. 62 | 63 | $ bash ../test.sh <<'EOF' 64 | > let _ = 65 | > match 'a' with 66 | > | 'a'..'z' -> () 67 | > | _ -> () 68 | > EOF 69 | let _ = 70 | match 'a' with 71 | | 'a' .. 'z' -> 72 | ___bisect_visit___ 0; 73 | () 74 | | _ -> 75 | ___bisect_visit___ 1; 76 | () 77 | 78 | 79 | Nullary polymorphic variand. 80 | 81 | $ bash ../test.sh <<'EOF' 82 | > let _ = 83 | > match `A with 84 | > | `A -> () 85 | > EOF 86 | let _ = 87 | match `A with 88 | | `A -> 89 | ___bisect_visit___ 0; 90 | () 91 | 92 | 93 | Polymorphic variant type name. 94 | 95 | $ bash ../test.sh <<'EOF' 96 | > type t = [ `A ] 97 | > let _ = 98 | > match `A with 99 | > | #t -> () 100 | > EOF 101 | type t = [ `A ] 102 | 103 | let _ = 104 | match `A with 105 | | #t -> 106 | ___bisect_visit___ 0; 107 | () 108 | 109 | 110 | Module. 111 | 112 | $ bash ../test.sh <<'EOF' 113 | > module type L = module type of List 114 | > let _ = 115 | > match (module List : L) with 116 | > | (module L) -> () 117 | > EOF 118 | module type L = module type of List 119 | 120 | let _ = 121 | match (module List : L) with 122 | | (module L) -> 123 | ___bisect_visit___ 0; 124 | () 125 | -------------------------------------------------------------------------------- /test/instrument/pattern/row.t: -------------------------------------------------------------------------------- 1 | Or-pattern instrumentation does not prevent row type generalization. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > type t = [ `A | `B ] 5 | > module M : 6 | > sig 7 | > val f : [< t ] -> unit 8 | > end = 9 | > struct 10 | > let f = function 11 | > | `A | `B -> () 12 | > end 13 | > EOF 14 | type t = [ `A | `B ] 15 | 16 | module M : sig 17 | val f : [< t ] -> unit 18 | end = struct 19 | let f = function 20 | | (`A | `B) as ___bisect_matched_value___ -> 21 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 22 | ___bisect_matched_value___ 23 | with 24 | | `A -> 25 | ___bisect_visit___ 0; 26 | () 27 | | `B -> 28 | ___bisect_visit___ 1; 29 | () 30 | | _ -> ()); 31 | () 32 | end 33 | -------------------------------------------------------------------------------- /test/instrument/pattern/unary.t: -------------------------------------------------------------------------------- 1 | Alias. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = 5 | > match `A with 6 | > | `A | `B as _x -> print_endline "foo" 7 | > EOF 8 | let _ = 9 | match `A with 10 | | (`A | `B) as _x as ___bisect_matched_value___ -> 11 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 12 | ___bisect_matched_value___ 13 | with 14 | | `A as _x -> 15 | ___bisect_visit___ 1; 16 | () 17 | | `B as _x -> 18 | ___bisect_visit___ 2; 19 | () 20 | | _ -> ()); 21 | ___bisect_post_visit___ 0 (print_endline "foo") 22 | 23 | 24 | Constructor. 25 | 26 | $ bash ../test.sh <<'EOF' 27 | > let _ = 28 | > match Some `A with 29 | > | Some (`A | `B) -> print_endline "foo" 30 | > | None -> () 31 | > EOF 32 | let _ = 33 | match Some `A with 34 | | Some (`A | `B) as ___bisect_matched_value___ -> 35 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 36 | ___bisect_matched_value___ 37 | with 38 | | Some `A -> 39 | ___bisect_visit___ 1; 40 | () 41 | | Some `B -> 42 | ___bisect_visit___ 2; 43 | () 44 | | _ -> ()); 45 | ___bisect_post_visit___ 0 (print_endline "foo") 46 | | None -> 47 | ___bisect_visit___ 3; 48 | () 49 | 50 | 51 | Polymorphic variant constructor. 52 | 53 | $ bash ../test.sh <<'EOF' 54 | > let _ = 55 | > match `A `B with 56 | > | `A (`B | `C) -> print_endline "foo" 57 | > EOF 58 | let _ = 59 | match `A `B with 60 | | `A (`B | `C) as ___bisect_matched_value___ -> 61 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 62 | ___bisect_matched_value___ 63 | with 64 | | `A `B -> 65 | ___bisect_visit___ 1; 66 | () 67 | | `A `C -> 68 | ___bisect_visit___ 2; 69 | () 70 | | _ -> ()); 71 | ___bisect_post_visit___ 0 (print_endline "foo") 72 | 73 | 74 | Type constraint. 75 | 76 | $ bash ../test.sh <<'EOF' 77 | > let _ = 78 | > match `A with 79 | > | (`A | `B : _) -> print_endline "foo" 80 | > EOF 81 | let _ = 82 | match `A with 83 | | (`A | `B : _) as ___bisect_matched_value___ -> 84 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 85 | ___bisect_matched_value___ 86 | with 87 | | (`A : _) -> 88 | ___bisect_visit___ 1; 89 | () 90 | | (`B : _) -> 91 | ___bisect_visit___ 2; 92 | () 93 | | _ -> ()); 94 | ___bisect_post_visit___ 0 (print_endline "foo") 95 | 96 | 97 | Lazy. 98 | 99 | $ bash ../test.sh <<'EOF' 100 | > let _ = 101 | > match lazy `A with 102 | > | lazy (`A | `B) -> print_endline "foo" 103 | > EOF 104 | let _ = 105 | match 106 | lazy 107 | (___bisect_visit___ 3; 108 | `A) 109 | with 110 | | (lazy (`A | `B)) as ___bisect_matched_value___ -> 111 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 112 | ___bisect_matched_value___ 113 | with 114 | | (lazy `A) -> 115 | ___bisect_visit___ 1; 116 | () 117 | | (lazy `B) -> 118 | ___bisect_visit___ 2; 119 | () 120 | | _ -> ()); 121 | ___bisect_post_visit___ 0 (print_endline "foo") 122 | -------------------------------------------------------------------------------- /test/instrument/pattern/when.t: -------------------------------------------------------------------------------- 1 | If there is a pattern guard, pattern instrumentation is placed on it instead. 2 | The nested expression gets a fresh instrumentation point, being the out-edge of 3 | the guard, rather than the pattern. 4 | 5 | $ bash ../test.sh <<'EOF' 6 | > let _ = 7 | > match `A `B with 8 | > | `A (`B | `C) when print_endline "foo"; true -> () 9 | > | _ -> () 10 | > EOF 11 | let _ = 12 | match `A `B with 13 | | `A (`B | `C) as ___bisect_matched_value___ 14 | when (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 15 | ___bisect_matched_value___ 16 | with 17 | | `A `B -> 18 | ___bisect_visit___ 1; 19 | () 20 | | `A `C -> 21 | ___bisect_visit___ 2; 22 | () 23 | | _ -> ()); 24 | ___bisect_post_visit___ 0 (print_endline "foo"); 25 | true -> 26 | ___bisect_visit___ 3; 27 | () 28 | | _ -> 29 | ___bisect_visit___ 4; 30 | () 31 | 32 | $ bash ../test.sh <<'EOF' 33 | > let _ = 34 | > match () with 35 | > | () -> () 36 | > | exception (Exit | Failure _) when print_endline "foo"; true -> () 37 | > EOF 38 | let _ = 39 | match () with 40 | | exception ((Exit | Failure _) as ___bisect_matched_value___) 41 | when (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 42 | ___bisect_matched_value___ 43 | with 44 | | Exit -> 45 | ___bisect_visit___ 2; 46 | () 47 | | Failure _ -> 48 | ___bisect_visit___ 3; 49 | () 50 | | _ -> ()); 51 | ___bisect_post_visit___ 0 (print_endline "foo"); 52 | true -> 53 | ___bisect_visit___ 4; 54 | () 55 | | () -> 56 | ___bisect_visit___ 1; 57 | () 58 | -------------------------------------------------------------------------------- /test/instrument/recent/dune: -------------------------------------------------------------------------------- 1 | (cram 2 | (deps ../test.sh)) 3 | -------------------------------------------------------------------------------- /test/instrument/recent/error.t: -------------------------------------------------------------------------------- 1 | Bad attributes generate an error message. 2 | 3 | $ echo "(lang dune 2.7)" > dune-project 4 | $ cat > dune <<'EOF' 5 | > (executable 6 | > (name test) 7 | > (modes byte) 8 | > (instrumentation (backend bisect_ppx))) 9 | > EOF 10 | $ cat > test.ml <<'EOF' 11 | > [@@@coverage invalid] 12 | > EOF 13 | $ dune build --instrument-with bisect_ppx --display quiet 14 | File "test.ml", line 1, characters 0-21: 15 | 1 | [@@@coverage invalid] 16 | ^^^^^^^^^^^^^^^^^^^^^ 17 | Error: Bad payload in coverage attribute. 18 | [1] 19 | 20 | 21 | Warnings 4 (fragile pattern matching due to wildcard) and 11 (unused match case) 22 | in generated case instrumentation are suppressed. Warning 26 (unused variable 23 | from as binding) is suppressed. Warning 28 (wildcard given to constant 24 | constructor) is suppressed. One instance is still displayed for the user's code. 25 | 26 | $ cat > test.ml <<'EOF' 27 | > type t = A | B | C 28 | > let _ = 29 | > match A with 30 | > | A | B -> () 31 | > | C -> () 32 | > let _ = 33 | > match A with 34 | > | A | B | C -> () 35 | > let _ = 36 | > match A with 37 | > | (A as x) | (B as x) -> x 38 | > | C -> C 39 | > let _ = 40 | > match A with 41 | > | A _ | B | C -> () 42 | > EOF 43 | $ dune build --instrument-with bisect_ppx --display quiet 2>&1 | sed -e 's/ \[[^]]*\]//g' 44 | File "test.ml", line 15, characters 6-7: 45 | 15 | | A _ | B | C -> () 46 | ^ 47 | Error (warning 28): wildcard pattern given as argument to a constant constructor 48 | 49 | 50 | Missing record labels warning (9) is suppressed from inserted documentation. It 51 | is still emitted for the user's code. 52 | 53 | $ cat > test.ml <<'EOF' 54 | > type t = {a : int; b : int} 55 | > let _ = 56 | > match {a = 0; b = 1} with 57 | > | {a} | {a} -> a 58 | > EOF 59 | $ dune build --instrument-with bisect_ppx --display quiet 2>&1 | sed -e 's/ \[[^]]*\]//g' 60 | File "test.ml", line 4, characters 4-7: 61 | 4 | | {a} | {a} -> a 62 | ^^^ 63 | Error (warning 9): the following labels are not bound in this record pattern: 64 | b 65 | Either bind these labels explicitly or add '; _' to the pattern. 66 | File "test.ml", line 4, characters 10-13: 67 | 4 | | {a} | {a} -> a 68 | ^^^ 69 | Error (warning 9): the following labels are not bound in this record pattern: 70 | b 71 | Either bind these labels explicitly or add '; _' to the pattern. 72 | File "test.ml", line 4, characters 10-13: 73 | 4 | | {a} | {a} -> a 74 | ^^^ 75 | Error (warning 12): this sub-pattern is unused. 76 | -------------------------------------------------------------------------------- /test/instrument/recent/exception-pattern.t: -------------------------------------------------------------------------------- 1 | Exception patterns under or-pattern. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = 5 | > match () with 6 | > | () -> () 7 | > | exception Exit | exception Failure _ -> () 8 | > EOF 9 | let _ = 10 | match () with 11 | | (exception (Exit as ___bisect_matched_value___)) 12 | | (exception (Failure _ as ___bisect_matched_value___)) -> 13 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 14 | ___bisect_matched_value___ 15 | with 16 | | Exit -> 17 | ___bisect_visit___ 1; 18 | () 19 | | Failure _ -> 20 | ___bisect_visit___ 2; 21 | () 22 | | _ -> ()); 23 | () 24 | | () -> 25 | ___bisect_visit___ 0; 26 | () 27 | 28 | 29 | Exception pattern under type constraint. 30 | 31 | $ bash ../test.sh <<'EOF' 32 | > let _ = 33 | > match () with 34 | > | () -> () 35 | > | (exception (Exit | Failure _) : unit) -> () 36 | > EOF 37 | let _ = 38 | match () with 39 | | ((exception ((Exit | Failure _) as ___bisect_matched_value___)) : unit) -> 40 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 41 | ___bisect_matched_value___ 42 | with 43 | | Exit -> 44 | ___bisect_visit___ 1; 45 | () 46 | | Failure _ -> 47 | ___bisect_visit___ 2; 48 | () 49 | | _ -> ()); 50 | () 51 | | () -> 52 | ___bisect_visit___ 0; 53 | () 54 | -------------------------------------------------------------------------------- /test/instrument/recent/exclusions.t: -------------------------------------------------------------------------------- 1 | $ echo > .ocamlformat 2 | $ echo "(lang dune 2.9)" > dune-project 3 | $ cat > dune <<'EOF' 4 | > (executables 5 | > (names not_excluded excluded_1) 6 | > (modes byte) 7 | > (ocamlc_flags -dsource) 8 | > (instrumentation 9 | > (backend bisect_ppx --exclusions bisect.exclude) (deps bisect.exclude))) 10 | > EOF 11 | $ cat > bisect.exclude <<'EOF' 12 | > file "excluded_1.ml" 13 | > EOF 14 | $ cat > not_excluded.ml <<'EOF' 15 | > let _f () = () 16 | > EOF 17 | $ cat > excluded_1.ml <<'EOF' 18 | > let _f () = () 19 | > EOF 20 | $ dune build ./not_excluded.bc --instrument-with bisect_ppx 2>&1 21 | [@@@ocaml.ppx.context 22 | { 23 | tool_name = "ppx_driver"; 24 | include_dirs = []; 25 | load_path = []; 26 | open_modules = []; 27 | for_package = None; 28 | debug = false; 29 | use_threads = false; 30 | use_vmthreads = false; 31 | recursive_types = false; 32 | principal = false; 33 | transparent_modules = false; 34 | unboxed_types = false; 35 | unsafe_string = false; 36 | cookies = [] 37 | }] 38 | [@@@ocaml.text "/*"] 39 | module Bisect_visit___not_excluded___ml = 40 | struct 41 | let ___bisect_visit___ = 42 | let points = [|12|] in 43 | let `Visit visit = 44 | Bisect.Runtime.register_file ~bisect_file:None ~bisect_silent:None 45 | ~filename:"not_excluded.ml" ~points ~bisect_sigterm:false in 46 | visit 47 | let ___bisect_post_visit___ point_index result = 48 | ___bisect_visit___ point_index; result 49 | end 50 | open Bisect_visit___not_excluded___ml 51 | [@@@ocaml.text "/*"] 52 | let _f () = ___bisect_visit___ 0; () 53 | 54 | $ dune build ./excluded_1.bc --instrument-with bisect_ppx 2>&1 55 | [@@@ocaml.ppx.context 56 | { 57 | tool_name = "ppx_driver"; 58 | include_dirs = []; 59 | load_path = []; 60 | open_modules = []; 61 | for_package = None; 62 | debug = false; 63 | use_threads = false; 64 | use_vmthreads = false; 65 | recursive_types = false; 66 | principal = false; 67 | transparent_modules = false; 68 | unboxed_types = false; 69 | unsafe_string = false; 70 | cookies = [] 71 | }] 72 | let _f () = () 73 | 74 | -------------------------------------------------------------------------------- /test/instrument/recent/gadt.t: -------------------------------------------------------------------------------- 1 | GADT. See https://github.com/aantron/bisect_ppx/issues/325. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > type _ t = A : unit t | B : bool t 5 | > let f : type a. a t -> unit = fun x -> 6 | > match x with 7 | > | A | B -> () 8 | > EOF 9 | type _ t = A : unit t | B : bool t 10 | 11 | let f : type a. a t -> unit = 12 | fun x -> 13 | ___bisect_visit___ 2; 14 | match x with 15 | | ___bisect_matched_value___ -> ( 16 | match ___bisect_matched_value___ with 17 | | A | B -> 18 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 19 | ___bisect_matched_value___ 20 | with 21 | | A -> 22 | ___bisect_visit___ 0; 23 | () 24 | | B -> 25 | ___bisect_visit___ 1; 26 | () 27 | | _ -> ()); 28 | ()) 29 | 30 | 31 | With function. 32 | 33 | $ bash ../test.sh <<'EOF' 34 | > type _ t = A : unit t | B : bool t 35 | > let f : type a. a t -> unit = function 36 | > | A | B -> () 37 | > EOF 38 | type _ t = A : unit t | B : bool t 39 | 40 | let f : type a. a t -> unit = 41 | fun ___bisect_matched_value___ -> 42 | match ___bisect_matched_value___ with 43 | | A | B -> 44 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 45 | ___bisect_matched_value___ 46 | with 47 | | A -> 48 | ___bisect_visit___ 0; 49 | () 50 | | B -> 51 | ___bisect_visit___ 1; 52 | () 53 | | _ -> ()); 54 | () 55 | -------------------------------------------------------------------------------- /test/instrument/recent/let-exception.t: -------------------------------------------------------------------------------- 1 | $ bash ../test.sh <<'EOF' 2 | > [@@@ocaml.warning "-38"] 3 | > let _ = 4 | > let exception E in print_endline "foo" 5 | > let _ = fun () -> 6 | > let exception E in print_endline "foo" 7 | > EOF 8 | [@@@ocaml.warning "-38"] 9 | 10 | let _ = 11 | let exception E in 12 | ___bisect_post_visit___ 0 (print_endline "foo") 13 | 14 | let _ = 15 | fun () -> 16 | ___bisect_visit___ 1; 17 | let exception E in 18 | print_endline "foo" 19 | -------------------------------------------------------------------------------- /test/instrument/recent/letop.t: -------------------------------------------------------------------------------- 1 | Subexpressions instrumented recursively. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let (let*) x f = f x 5 | > let (and*) x y = (x, y) 6 | > let return x = x 7 | > let _ = 8 | > let* () = print_endline "foo" 9 | > and* () = print_endline "bar" in 10 | > let* () = print_endline "baz" in 11 | > return () 12 | > EOF 13 | let ( let* ) x f = 14 | ___bisect_visit___ 0; 15 | f x 16 | 17 | let ( and* ) x y = 18 | ___bisect_visit___ 1; 19 | (x, y) 20 | 21 | let return x = 22 | ___bisect_visit___ 2; 23 | x 24 | 25 | let _ = 26 | let* () = ___bisect_post_visit___ 3 (print_endline "foo") 27 | and* () = ___bisect_post_visit___ 4 (print_endline "bar") in 28 | ___bisect_visit___ 7; 29 | let* () = ___bisect_post_visit___ 5 (print_endline "baz") in 30 | ___bisect_visit___ 6; 31 | return () 32 | -------------------------------------------------------------------------------- /test/instrument/recent/opaque_identity.t: -------------------------------------------------------------------------------- 1 | Sys.opaque_identity instrumentation is suppressed. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > let _ = Sys.opaque_identity (print_endline "foo") 5 | > EOF 6 | let _ = Sys.opaque_identity (___bisect_post_visit___ 0 (print_endline "foo")) 7 | -------------------------------------------------------------------------------- /test/instrument/recent/pattern-open.t: -------------------------------------------------------------------------------- 1 | Or-pattern under local open. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > module M = struct exception E end 5 | > let _ = 6 | > match () with 7 | > | () -> () 8 | > | M.(exception (E | Exit)) -> () 9 | > EOF 10 | module M = struct 11 | exception E 12 | end 13 | 14 | let _ = 15 | match () with 16 | | M.((exception ((E | Exit) as ___bisect_matched_value___))) -> 17 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 18 | ___bisect_matched_value___ 19 | with 20 | | M.(E) -> 21 | ___bisect_visit___ 1; 22 | () 23 | | M.(Exit) -> 24 | ___bisect_visit___ 2; 25 | () 26 | | _ -> ()); 27 | () 28 | | () -> 29 | ___bisect_visit___ 0; 30 | () 31 | -------------------------------------------------------------------------------- /test/instrument/recent/react.t: -------------------------------------------------------------------------------- 1 | Let-bindings with [@@react.component] are not instrumented at their top level. 2 | 3 | $ bash ../test.sh <<'EOF' 4 | > module React = struct let forwardRef f = f () end 5 | > 6 | > let make1 = fun () -> ignore ignore 7 | > [@@react.component] 8 | > 9 | > let make2 () = ignore ignore 10 | > [@@react.component] 11 | > 12 | > let make3 = React.forwardRef (fun r -> ignore r) 13 | > [@@react.component] 14 | > EOF 15 | module React = struct 16 | let forwardRef f = 17 | ___bisect_visit___ 0; 18 | f () 19 | end 20 | 21 | let make1 () = 22 | ___bisect_visit___ 1; 23 | ignore ignore 24 | [@@react.component] 25 | 26 | let make2 () = 27 | ___bisect_visit___ 2; 28 | ignore ignore 29 | [@@react.component] 30 | 31 | let make3 = 32 | React.forwardRef (fun r -> 33 | ___bisect_visit___ 3; 34 | ignore r) 35 | [@@react.component] 36 | -------------------------------------------------------------------------------- /test/instrument/recent/refutation.t: -------------------------------------------------------------------------------- 1 | Refutation cases must not be instrumented in order to still be recognized by the 2 | compiler. 3 | 4 | $ bash ../test.sh <<'EOF' 5 | > let _ = 6 | > match `A with 7 | > | `A | `B -> () 8 | > | `A | `B -> . 9 | > EOF 10 | let _ = 11 | match `A with 12 | | (`A | `B) as ___bisect_matched_value___ -> 13 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 14 | ___bisect_matched_value___ 15 | with 16 | | `A -> 17 | ___bisect_visit___ 0; 18 | () 19 | | `B -> 20 | ___bisect_visit___ 1; 21 | () 22 | | _ -> ()); 23 | () 24 | | `A | `B -> . 25 | 26 | 27 | assert false gets special treatment by the compiler and must not be 28 | instrumented. 29 | 30 | $ bash ../test.sh <<'EOF' 31 | > let _ = 32 | > match `A with 33 | > | `A | `B -> () 34 | > | `C | `D -> assert false 35 | > EOF 36 | let _ = 37 | match `A with 38 | | (`A | `B) as ___bisect_matched_value___ -> 39 | (match[@ocaml.warning "-4-8-9-11-26-27-28-33"] 40 | ___bisect_matched_value___ 41 | with 42 | | `A -> 43 | ___bisect_visit___ 0; 44 | () 45 | | `B -> 46 | ___bisect_visit___ 1; 47 | () 48 | | _ -> ()); 49 | () 50 | | `C | `D -> assert false 51 | 52 | 53 | assert false exception cases don't get instrumented. 54 | 55 | $ bash ../test.sh <<'EOF' 56 | > let _ = 57 | > match `A with 58 | > | `A -> () 59 | > | exception Not_found -> assert false 60 | > | exception Invalid_argument _ | exception Exit -> assert false 61 | > EOF 62 | let _ = 63 | match `A with 64 | | exception Not_found -> assert false 65 | | (exception Invalid_argument _) | (exception Exit) -> assert false 66 | | `A -> 67 | ___bisect_visit___ 0; 68 | () 69 | -------------------------------------------------------------------------------- /test/instrument/shadow.t: -------------------------------------------------------------------------------- 1 | If Bisect's instrumentation helpers aren't properly named/structured in modules, 2 | opening a user's module can shadow one module's instrumentation helpers by 3 | another's. This test relies on OCaml's shadowing warnings to make sure that 4 | shadowing is not occurring. 5 | 6 | $ echo "(lang dune 2.7)" > dune-project 7 | $ cat > dune <<'EOF' 8 | > (executable 9 | > (name test) 10 | > (modes byte) 11 | > (instrumentation (backend bisect_ppx))) 12 | > EOF 13 | $ cat > test.ml <<'EOF' 14 | > [@@@ocaml.warning "+44"] 15 | > open Helper 16 | > let f () = g () 17 | > EOF 18 | $ cat > helper.ml <<'EOF' 19 | > let g () = () 20 | > EOF 21 | $ dune build --instrument-with bisect_ppx --display quiet 22 | -------------------------------------------------------------------------------- /test/instrument/structure.t: -------------------------------------------------------------------------------- 1 | An empty file. Show the bare registration code. 2 | 3 | $ bash test.sh --include-registration <<'EOF' 4 | > 5 | > EOF 6 | module Bisect_visit___test___ml = struct 7 | let ___bisect_visit___ = 8 | let points = [||] in 9 | let (`Visit visit) = 10 | Bisect.Runtime.register_file ~bisect_file:None ~bisect_silent:None 11 | ~filename:"test.ml" ~points ~bisect_sigterm:false 12 | in 13 | visit 14 | 15 | let ___bisect_post_visit___ point_index result = 16 | ___bisect_visit___ point_index; 17 | result 18 | end 19 | 20 | open Bisect_visit___test___ml 21 | 22 | [@@@ocaml.text "/*"] 23 | -------------------------------------------------------------------------------- /test/instrument/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "(lang dune 2.7)" > dune-project 4 | 5 | echo "(executable" > dune 6 | echo " (name test)" >> dune 7 | echo " (modes byte)" >> dune 8 | echo " (ocamlc_flags -dsource)" >> dune 9 | echo " (instrumentation (backend bisect_ppx)))" >> dune 10 | 11 | echo > .ocamlformat 12 | 13 | rm -f test.ml 14 | while read line 15 | do 16 | echo "$line" >> test.ml 17 | done < /dev/stdin 18 | 19 | sanitize() { 20 | # [@@@ocaml.text "/*"] is the delimiter in the output. Bisect_ppx runtime 21 | # registration code begins at the first line containing that text. The 22 | # instrumented module proper begins after the second such line. 23 | 24 | THRESHOLD=${1:-2} 25 | COUNT=0 26 | 27 | while read line 28 | do 29 | if [ $COUNT -ge $THRESHOLD ] 30 | then 31 | echo "$line" 32 | fi 33 | 34 | if [ "$line" == "[@@@ocaml.text \"/*\"]" ] 35 | then 36 | COUNT=$(($COUNT + 1)) 37 | fi 38 | done 39 | } 40 | 41 | if [ "$1" == "--include-registration" ] 42 | then 43 | DELIMITERS=1 44 | fi 45 | 46 | dune build ./test.bc --instrument-with bisect_ppx 2>&1 \ 47 | | sanitize $DELIMITERS \ 48 | | ocamlformat --name test.ml - 49 | -------------------------------------------------------------------------------- /test/js/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : test 2 | test : 3 | rm -rf _coverage *.coverage 4 | npm run instrument 5 | npm run execute 6 | ./node_modules/.bin/bisect-ppx-report html 7 | ./node_modules/.bin/bisect-ppx-report summary | diff expected - 8 | ./node_modules/.bin/bisect-ppx-report summary 9 | @echo "See _coverage/index.html." 10 | 11 | .PHONY : full-test 12 | full-test : clean install test 13 | 14 | .PHONY : install 15 | install : 16 | npm pack ../.. 17 | tar xf *.tgz 18 | npm install 19 | npm list || true 20 | 21 | .PHONY : clean 22 | clean : 23 | rm -rf node_modules lib _esy *.tgz package *.coverage _coverage \ 24 | package-lock.json 25 | 26 | .PHONY : clean-for-caching 27 | clean-for-caching : 28 | npm uninstall --no-save bisect_ppx || true 29 | rm -rf node_modules/bisect_ppx 30 | -------------------------------------------------------------------------------- /test/js/bsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bisect_ppx-test", 3 | "bs-dependencies": [ 4 | "bisect_ppx" 5 | ], 6 | "ppx-flags": [ 7 | "bisect_ppx/ppx" 8 | ], 9 | "refmt": 3, 10 | "sources": ["."] 11 | } 12 | -------------------------------------------------------------------------------- /test/js/expected: -------------------------------------------------------------------------------- 1 | Coverage: 1/1 (100.00%) 2 | -------------------------------------------------------------------------------- /test/js/hello.re: -------------------------------------------------------------------------------- 1 | let () = { 2 | print_endline("Hello, world!"); 3 | }; 4 | -------------------------------------------------------------------------------- /test/js/hello.rei: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aantron/bisect_ppx/dc29402f2f8b6246150b18ac2e7e1d48e578a106/test/js/hello.rei -------------------------------------------------------------------------------- /test/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bisect_ppx-test", 3 | "dependencies": { 4 | "bisect_ppx": "file:package", 5 | "rescript": "^10", 6 | "esy": "^0.6.7" 7 | }, 8 | "scripts": { 9 | "build": "rescript", 10 | "instrument": "BISECT_ENABLE=yes rescript", 11 | "execute": "node ./lib/js/hello.js", 12 | "clean": "rescript -clean-world" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/random.t: -------------------------------------------------------------------------------- 1 | Bisect's runtime does not clobber the global random number generator state. 2 | 3 | $ echo "(lang dune 2.7)" > dune-project 4 | $ cat > dune <<'EOF' 5 | > (executable 6 | > (name random_test) 7 | > (instrumentation (backend bisect_ppx))) 8 | > EOF 9 | $ cat > random_test.ml <<'EOF' 10 | > let () = 11 | > Random.int 1000 |> string_of_int |> print_endline 12 | > EOF 13 | $ dune exec ./random_test.exe > pristine 14 | $ dune exec ./random_test.exe --instrument-with bisect_ppx > 1 15 | $ dune exec ./random_test.exe --instrument-with bisect_ppx > 2 16 | $ diff pristine 1 17 | $ diff pristine 2 18 | -------------------------------------------------------------------------------- /test/report/cobertura.t: -------------------------------------------------------------------------------- 1 | $ echo "(lang dune 2.7)" > dune-project 2 | $ cat > dune <<'EOF' 3 | > (executable 4 | > (name test) 5 | > (instrumentation (backend bisect_ppx))) 6 | > EOF 7 | $ dune exec ./test.exe --instrument-with bisect_ppx 8 | $ bisect-ppx-report cobertura coverage.xml --verbose 9 | Info: found *.coverage files in './' 10 | $ cat coverage.xml 11 | 12 | 13 | 14 | . 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/report/directory.t: -------------------------------------------------------------------------------- 1 | Reporter fails to create the output directory. 2 | 3 | $ echo "(lang dune 2.7)" > dune-project 4 | $ cat > dune <<'EOF' 5 | > (executable 6 | > (name test) 7 | > (instrumentation (backend bisect_ppx))) 8 | > EOF 9 | $ dune exec ./test.exe --instrument-with bisect_ppx 10 | $ touch foo 11 | $ bisect-ppx-report html -o foo/bar/ 12 | Error: cannot create directory 'foo/bar/': Not a directory 13 | [1] 14 | 15 | 16 | Reporter fails to create intermediate directory. 17 | 18 | $ echo "(lang dune 2.7)" > dune-project 19 | $ cat > dune <<'EOF' 20 | > (executable 21 | > (name test) 22 | > (instrumentation (backend bisect_ppx))) 23 | > EOF 24 | $ dune exec ./test.exe --instrument-with bisect_ppx 25 | $ touch foo 26 | $ bisect-ppx-report html -o foo/bar/baz/ 27 | Error: cannot create directory 'foo/bar': Not a directory 28 | [1] 29 | -------------------------------------------------------------------------------- /test/report/dune: -------------------------------------------------------------------------------- 1 | (cram 2 | (deps test.ml merge.ml test_merge1.ml test_merge2.ml empty.ml) 3 | (alias compatible)) 4 | -------------------------------------------------------------------------------- /test/report/empty.ml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aantron/bisect_ppx/dc29402f2f8b6246150b18ac2e7e1d48e578a106/test/report/empty.ml -------------------------------------------------------------------------------- /test/report/html-tree/baz/baz.ml: -------------------------------------------------------------------------------- 1 | let a () = () 2 | 3 | let b () = () 4 | -------------------------------------------------------------------------------- /test/report/html-tree/dune: -------------------------------------------------------------------------------- 1 | (cram 2 | (deps test_tree.ml (source_tree foo) (source_tree baz)) 3 | (alias compatible)) 4 | -------------------------------------------------------------------------------- /test/report/html-tree/foo/bar/bar_a.ml: -------------------------------------------------------------------------------- 1 | let a () = () 2 | 3 | let b () = () 4 | -------------------------------------------------------------------------------- /test/report/html-tree/foo/bar/bar_b.ml: -------------------------------------------------------------------------------- 1 | let a () = () 2 | 3 | let b () = () 4 | -------------------------------------------------------------------------------- /test/report/html-tree/foo/foo.ml: -------------------------------------------------------------------------------- 1 | let a () = () 2 | 3 | let b () = () 4 | -------------------------------------------------------------------------------- /test/report/html-tree/test_tree.ml: -------------------------------------------------------------------------------- 1 | let () = 2 | Foo.a () ; Foo.b () ; Bar_a.a () ; Bar_a.b () ; Bar_b.a () ; Baz.b () 3 | -------------------------------------------------------------------------------- /test/report/html.t: -------------------------------------------------------------------------------- 1 | $ echo "(lang dune 2.7)" > dune-project 2 | $ cat > dune <<'EOF' 3 | > (executable 4 | > (name test) 5 | > (instrumentation (backend bisect_ppx))) 6 | > EOF 7 | $ dune exec ./test.exe --instrument-with bisect_ppx 8 | $ bisect-ppx-report html --verbose 9 | Info: found *.coverage files in './' 10 | Info: Writing index file... 11 | $ ls _coverage | sort 12 | coverage.css 13 | coverage.js 14 | highlight.pack.js 15 | index.html 16 | test.ml.html 17 | $ cat _coverage/index.html 18 | 19 | 20 | 21 | 22 | Coverage report 23 | 24 | 25 | 26 | 27 | 31 |
32 |
33 | 34 | 35 | 36 | 33% (2 / 6) 37 | 38 | test.ml 39 | 40 |
41 |
42 | 43 | 44 | $ cat _coverage/test.ml.html 45 | 46 | 47 | 48 | 49 | test.ml — Coverage report 50 | 51 | 52 | 53 | 54 | 55 | 56 | 64 | 70 |
71 |
72 |
 73 |    
 74 |    
 75 |    
 76 |    
 77 |    
 78 |    
 79 |    
 80 |    
 81 |    
 82 |    
 83 |    
 84 |    
 85 |    
 86 |    
 87 |    
 88 |    
 89 |   
90 |
91 |
92 |
 93 |    1
 94 |    2
 95 |    3
 96 |    4
 97 |    5
 98 |    6
 99 |    7
100 |    8
101 |    9
102 |   10
103 |   11
104 |   12
105 |   13
106 |   14
107 |   15
108 |   16
109 |   
110 |
let f () =
111 |     ()
112 |   
113 |   let g () =
114 |     ()
115 |   
116 |   let () =
117 |     f ()
118 |   
119 |   (* Reproduces a HTML display bug that existed in development between 2.6.3 and
120 |      2.7.0, starting with 1b8d7ec5985aa12a85e797e3d53fc72713e80c35. *)
121 |   let a () =
122 |     if true then
123 |       true
124 |     else
125 |       false
126 |   
127 |
128 |
129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /test/report/line.t: -------------------------------------------------------------------------------- 1 | Reporter still works even in the presence of line number directives. 2 | 3 | $ echo "(lang dune 2.7)" > dune-project 4 | $ cat > dune <<'EOF' 5 | > (executable 6 | > (name test2) 7 | > (instrumentation (backend bisect_ppx))) 8 | > EOF 9 | $ cat test.ml - >> test2.ml <<'EOF' 10 | > 11 | > # 1 "other_file.ml" 12 | > 13 | > let h () = 14 | > () 15 | > 16 | > let () = 17 | > h () 18 | > EOF 19 | $ cat test2.ml 20 | let f () = 21 | () 22 | 23 | let g () = 24 | () 25 | 26 | let () = 27 | f () 28 | 29 | (* Reproduces a HTML display bug that existed in development between 2.6.3 and 30 | 2.7.0, starting with 1b8d7ec5985aa12a85e797e3d53fc72713e80c35. *) 31 | let a () = 32 | if true then 33 | true 34 | else 35 | false 36 | 37 | # 1 "other_file.ml" 38 | 39 | let h () = 40 | () 41 | 42 | let () = 43 | h () 44 | $ dune exec ./test2.exe --instrument-with bisect_ppx 45 | $ bisect-ppx-report html --verbose 46 | Info: found *.coverage files in './' 47 | Info: Writing index file... 48 | $ cat _coverage/test2.ml.html 49 | 50 | 51 | 52 | 53 | test2.ml — Coverage report 54 | 55 | 56 | 57 | 58 | 59 | 60 | 68 | 74 |
75 |
76 |
 77 |    
 78 |    
 79 |    
 80 |    
 81 |    
 82 |    
 83 |    
 84 |    
 85 |    
 86 |    
 87 |    
 88 |    
 89 |    
 90 |    
 91 |    
 92 |    
 93 |    
 94 |    
 95 |    
 96 |    
 97 |    
 98 |    
 99 |    
100 |    
101 |   
102 |
103 |
104 |
105 |    1
106 |    2
107 |    3
108 |    4
109 |    5
110 |    6
111 |    7
112 |    8
113 |    9
114 |   10
115 |   11
116 |   12
117 |   13
118 |   14
119 |   15
120 |   16
121 |   17
122 |   18
123 |   19
124 |   20
125 |   21
126 |   22
127 |   23
128 |   24
129 |   
130 |
let f () =
131 |     ()
132 |   
133 |   let g () =
134 |     ()
135 |   
136 |   let () =
137 |     f ()
138 |   
139 |   (* Reproduces a HTML display bug that existed in development between 2.6.3 and
140 |      2.7.0, starting with 1b8d7ec5985aa12a85e797e3d53fc72713e80c35. *)
141 |   let a () =
142 |     if true then
143 |       true
144 |     else
145 |       false
146 |   
147 |   # 1 "other_file.ml"
148 |   
149 |   let h () =
150 |     ()
151 |   
152 |   let () =
153 |     h ()
154 |   
155 |
156 |
157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /test/report/merge.ml: -------------------------------------------------------------------------------- 1 | let rec sum = function 2 | | [] -> 0 3 | | x :: xs -> x + sum xs 4 | 5 | let rec product = function 6 | | [] -> 1 7 | | x :: xs -> x * product xs 8 | 9 | -------------------------------------------------------------------------------- /test/report/merge.t: -------------------------------------------------------------------------------- 1 | Merge two files 2 | 3 | $ echo "(lang dune 2.7)" > dune-project 4 | $ cat > dune <<'EOF' 5 | > (executables 6 | > (names test_merge1 test_merge2) 7 | > (instrumentation (backend bisect_ppx))) 8 | > EOF 9 | $ dune exec ./test_merge1.exe --instrument-with bisect_ppx 10 | $ bisect-ppx-report summary --per-file 11 | 16.67 % 1/6 merge.ml 12 | 100.00 % 2/2 test_merge1.ml 13 | 37.50 % 3/8 Project coverage 14 | $ dune exec ./test_merge2.exe --instrument-with bisect_ppx 15 | $ bisect-ppx-report summary --per-file 16 | 33.33 % 2/6 merge.ml 17 | 100.00 % 2/2 test_merge1.ml 18 | 100.00 % 2/2 test_merge2.ml 19 | 60.00 % 6/10 Project coverage 20 | $ bisect-ppx-report merge merged.temp 21 | $ rm -rf _build; rm *.coverage; mv merged.temp merged.coverage 22 | $ bisect-ppx-report summary --per-file 23 | 33.33 % 2/6 merge.ml 24 | 100.00 % 2/2 test_merge1.ml 25 | 100.00 % 2/2 test_merge2.ml 26 | 60.00 % 6/10 Project coverage 27 | -------------------------------------------------------------------------------- /test/report/missing.t: -------------------------------------------------------------------------------- 1 | Missing files trigger a neat error. 2 | 3 | $ echo "(lang dune 2.7)" > dune-project 4 | $ cat > dune <<'EOF' 5 | > (executable 6 | > (name test) 7 | > (instrumentation (backend bisect_ppx))) 8 | > EOF 9 | $ dune exec ./test.exe --instrument-with bisect_ppx 10 | $ rm -rf _build 11 | $ mv test.ml test2.ml 12 | $ bisect-ppx-report html 13 | Error: cannot find source file 'test.ml' in: 14 | - . 15 | - ./_build/default 16 | Hint: consider passing --ignore-missing-files. 17 | [1] 18 | 19 | 20 | --ignore-missing-files turns this error into a warning. 21 | 22 | $ echo "(lang dune 2.7)" > dune-project 23 | $ cat > dune <<'EOF' 24 | > (executable 25 | > (name test) 26 | > (instrumentation (backend bisect_ppx))) 27 | > EOF 28 | $ mv test2.ml test.ml 29 | $ dune exec ./test.exe --instrument-with bisect_ppx 30 | $ rm -rf _build 31 | $ mv test.ml test2.ml 32 | $ bisect-ppx-report html --ignore-missing-files --verbose 33 | Info: found *.coverage files in './' 34 | Info: cannot find source file 'test.ml' in: 35 | - . 36 | - ./_build/default 37 | Info: Writing index file... 38 | 39 | 40 | The warning is visible only when --verbose is provided. 41 | 42 | $ echo "(lang dune 2.7)" > dune-project 43 | $ cat > dune <<'EOF' 44 | > (executable 45 | > (name test) 46 | > (instrumentation (backend bisect_ppx))) 47 | > EOF 48 | $ mv test2.ml test.ml 49 | $ dune exec ./test.exe --instrument-with bisect_ppx 50 | $ rm -rf _build 51 | $ mv test.ml test2.ml 52 | $ bisect-ppx-report html --ignore-missing-files 53 | -------------------------------------------------------------------------------- /test/report/test.ml: -------------------------------------------------------------------------------- 1 | let f () = 2 | () 3 | 4 | let g () = 5 | () 6 | 7 | let () = 8 | f () 9 | 10 | (* Reproduces a HTML display bug that existed in development between 2.6.3 and 11 | 2.7.0, starting with 1b8d7ec5985aa12a85e797e3d53fc72713e80c35. *) 12 | let a () = 13 | if true then 14 | true 15 | else 16 | false 17 | -------------------------------------------------------------------------------- /test/report/test_merge1.ml: -------------------------------------------------------------------------------- 1 | let () = assert (Merge.sum [] = 0) 2 | -------------------------------------------------------------------------------- /test/report/test_merge2.ml: -------------------------------------------------------------------------------- 1 | let () = assert (Merge.product [] = 1) 2 | -------------------------------------------------------------------------------- /test/report/text.t: -------------------------------------------------------------------------------- 1 | Summary. 2 | 3 | $ echo "(lang dune 2.7)" > dune-project 4 | $ cat > dune <<'EOF' 5 | > (executable 6 | > (name test) 7 | > (instrumentation (backend bisect_ppx))) 8 | > EOF 9 | $ dune exec ./test.exe --instrument-with bisect_ppx 10 | $ bisect-ppx-report summary --verbose 11 | Info: found *.coverage files in './' 12 | Coverage: 2/6 (33.33%) 13 | 14 | 15 | Per-file coverage. 16 | 17 | $ rm -rf *.coverage 18 | $ echo "(lang dune 2.7)" > dune-project 19 | $ cat > dune <<'EOF' 20 | > (executable 21 | > (name test) 22 | > (modules test) 23 | > (instrumentation (backend bisect_ppx))) 24 | > 25 | > (executable 26 | > (name empty) 27 | > (modules empty) 28 | > (instrumentation (backend bisect_ppx))) 29 | > EOF 30 | $ dune exec ./test.exe --instrument-with bisect_ppx 31 | $ dune exec ./empty.exe --instrument-with bisect_ppx 32 | $ bisect-ppx-report summary --per-file --verbose 33 | Info: found *.coverage files in './' 34 | 100.00 % 0/0 empty.ml 35 | 33.33 % 2/6 test.ml 36 | 33.33 % 2/6 Project coverage 37 | -------------------------------------------------------------------------------- /test/report/truncate.t: -------------------------------------------------------------------------------- 1 | A successful run with a complete .coverage file. 2 | 3 | $ echo "(lang dune 2.7)" > dune-project 4 | $ cat > dune <<'EOF' 5 | > (executable 6 | > (name test) 7 | > (instrumentation (backend bisect_ppx))) 8 | > EOF 9 | $ dune exec ./test.exe --instrument-with bisect_ppx 10 | $ mv bisect*.coverage bisect0.coverage 11 | $ cat bisect0.coverage 12 | BISECT-COVERAGE-4 1 7 test.ml 6 13 30 45 245 229 212 6 1 0 1 0 0 0 13 | $ bisect-ppx-report summary --verbose 14 | Info: found *.coverage files in './' 15 | Coverage: 2/6 (33.33%) 16 | 17 | 18 | Truncate the .coverage file, clipping one of the integer arrays. 19 | 20 | $ truncate -c -s 56 bisect0.coverage 21 | $ cat bisect0.coverage 22 | BISECT-COVERAGE-4 1 7 test.ml 6 13 30 45 245 229 212 6 1 23 | $ bisect-ppx-report summary --verbose 24 | Info: found *.coverage files in './' 25 | Error: cannot read coverage file 'bisect0.coverage': bad integer 26 | [1] 27 | 28 | $ truncate -c -s 55 bisect0.coverage 29 | $ cat bisect0.coverage 30 | BISECT-COVERAGE-4 1 7 test.ml 6 13 30 45 245 229 212 6 31 | $ bisect-ppx-report summary --verbose 32 | Info: found *.coverage files in './' 33 | Error: cannot read coverage file 'bisect0.coverage': bad integer 34 | [1] 35 | 36 | $ truncate -c -s 54 bisect0.coverage 37 | $ cat bisect0.coverage 38 | BISECT-COVERAGE-4 1 7 test.ml 6 13 30 45 245 229 212 6 39 | $ bisect-ppx-report summary --verbose 40 | Info: found *.coverage files in './' 41 | Error: cannot read coverage file 'bisect0.coverage': bad integer 42 | [1] 43 | 44 | 45 | Truncate the whole array. 46 | 47 | $ truncate -c -s 53 bisect0.coverage 48 | $ cat bisect0.coverage 49 | BISECT-COVERAGE-4 1 7 test.ml 6 13 30 45 245 229 212 50 | $ bisect-ppx-report summary --verbose 51 | Info: found *.coverage files in './' 52 | Error: cannot read coverage file 'bisect0.coverage': bad integer 53 | [1] 54 | 55 | $ truncate -c -s 52 bisect0.coverage 56 | $ cat bisect0.coverage 57 | BISECT-COVERAGE-4 1 7 test.ml 6 13 30 45 245 229 212 58 | $ bisect-ppx-report summary --verbose 59 | Info: found *.coverage files in './' 60 | Error: cannot read coverage file 'bisect0.coverage': bad integer 61 | [1] 62 | 63 | 64 | Truncate a string. 65 | 66 | $ truncate -c -s 30 bisect0.coverage 67 | $ cat bisect0.coverage 68 | BISECT-COVERAGE-4 1 7 test.ml 69 | $ bisect-ppx-report summary --verbose 70 | Info: found *.coverage files in './' 71 | Error: cannot read coverage file 'bisect0.coverage': bad integer 72 | [1] 73 | 74 | $ truncate -c -s 29 bisect0.coverage 75 | $ cat bisect0.coverage 76 | BISECT-COVERAGE-4 1 7 test.ml 77 | $ bisect-ppx-report summary --verbose 78 | Info: found *.coverage files in './' 79 | Error: cannot read coverage file 'bisect0.coverage': bad integer 80 | [1] 81 | 82 | $ truncate -c -s 28 bisect0.coverage 83 | $ cat bisect0.coverage 84 | BISECT-COVERAGE-4 1 7 test.m 85 | $ bisect-ppx-report summary --verbose 86 | Info: found *.coverage files in './' 87 | Error: coverage file 'bisect0.coverage' is truncated 88 | [1] 89 | 90 | $ truncate -c -s 22 bisect0.coverage 91 | $ cat bisect0.coverage 92 | BISECT-COVERAGE-4 1 7 93 | $ bisect-ppx-report summary --verbose 94 | Info: found *.coverage files in './' 95 | Error: coverage file 'bisect0.coverage' is truncated 96 | [1] 97 | 98 | $ truncate -c -s 21 bisect0.coverage 99 | $ cat bisect0.coverage 100 | BISECT-COVERAGE-4 1 7 101 | $ bisect-ppx-report summary --verbose 102 | Info: found *.coverage files in './' 103 | Error: coverage file 'bisect0.coverage' is truncated 104 | [1] 105 | 106 | $ truncate -c -s 20 bisect0.coverage 107 | $ cat bisect0.coverage 108 | BISECT-COVERAGE-4 1 109 | $ bisect-ppx-report summary --verbose 110 | Info: found *.coverage files in './' 111 | Error: cannot read coverage file 'bisect0.coverage': bad integer 112 | [1] 113 | 114 | 115 | Truncate the file header. 116 | 117 | $ truncate -c -s 18 bisect0.coverage 118 | $ cat bisect0.coverage 119 | BISECT-COVERAGE-4 120 | $ bisect-ppx-report summary --verbose 121 | Info: found *.coverage files in './' 122 | Error: cannot read coverage file 'bisect0.coverage': bad integer 123 | [1] 124 | 125 | $ truncate -c -s 17 bisect0.coverage 126 | $ cat bisect0.coverage 127 | BISECT-COVERAGE-4 128 | $ bisect-ppx-report summary --verbose 129 | Info: found *.coverage files in './' 130 | Error: cannot read coverage file 'bisect0.coverage': bad integer 131 | [1] 132 | 133 | $ truncate -c -s 16 bisect0.coverage 134 | $ cat bisect0.coverage 135 | BISECT-COVERAGE- 136 | $ bisect-ppx-report summary --verbose 137 | Info: found *.coverage files in './' 138 | Error: coverage file 'bisect0.coverage' is truncated 139 | [1] 140 | 141 | 142 | Truncate the whole file. 143 | 144 | $ truncate -c -s 0 bisect0.coverage 145 | $ cat bisect0.coverage 146 | $ bisect-ppx-report summary --verbose 147 | Info: found *.coverage files in './' 148 | Error: coverage file 'bisect0.coverage' is truncated 149 | [1] 150 | -------------------------------------------------------------------------------- /test/self/bisect_ppx.diff: -------------------------------------------------------------------------------- 1 | diff -ru src/common/dune _self/bisect_ppx/src/common/dune 2 | --- src/common/dune 3 | +++ _self/bisect_ppx/src/common/dune 4 | @@ -1,4 +1,5 @@ 5 | (library 6 | (name bisect_common) 7 | (public_name bisect_ppx.common) 8 | + (instrumentation (backend meta_bisect_ppx)) 9 | (synopsis "Bisect_ppx internal functions (internal)")) 10 | diff -ru src/ppx/dune _self/bisect_ppx/src/ppx/dune 11 | --- src/ppx/dune 12 | +++ _self/bisect_ppx/src/ppx/dune 13 | @@ -10,4 +10,5 @@ 14 | (ppx_runtime_libraries bisect_ppx.runtime) 15 | (preprocess (pps ppxlib.metaquot)) 16 | (flags (:standard -open Ocaml_shadow)) 17 | + (instrumentation (backend meta_bisect_ppx)) 18 | (libraries bisect_ppx.common ppxlib str)) 19 | diff -ru src/report/dune _self/bisect_ppx/src/report/dune 20 | --- src/report/dune 21 | +++ _self/bisect_ppx/src/report/dune 22 | @@ -2,6 +2,7 @@ 23 | (name main) 24 | (public_name bisect-ppx-report) 25 | (package bisect_ppx) 26 | + (instrumentation (backend meta_bisect_ppx)) 27 | (libraries bisect_ppx.common cmdliner unix)) 28 | 29 | (rule 30 | diff -ru src/runtime/native/dune _self/bisect_ppx/src/runtime/native/dune 31 | --- src/runtime/native/dune 32 | +++ _self/bisect_ppx/src/runtime/native/dune 33 | @@ -2,4 +2,5 @@ 34 | (name bisect) 35 | (public_name bisect_ppx.runtime) 36 | (synopsis "Bisect_ppx runtime library (internal)") 37 | + (instrumentation (backend meta_bisect_ppx)) 38 | (libraries bisect_ppx.common unix)) 39 | -------------------------------------------------------------------------------- /test/sigterm/at_exit_hook.ml: -------------------------------------------------------------------------------- 1 | [@@@coverage exclude_file] 2 | 3 | let is_child = ref false 4 | 5 | let () = 6 | at_exit (fun () -> 7 | if !is_child then begin 8 | Unix.sleep 5; 9 | print_endline "I should've been woken up by now." 10 | end) 11 | -------------------------------------------------------------------------------- /test/sigterm/at_exit_main.ml: -------------------------------------------------------------------------------- 1 | let () = 2 | match Unix.fork () with 3 | | 0 -> 4 | At_exit_hook.is_child := true 5 | | child -> 6 | Unix.sleep 1; 7 | Unix.kill child Sys.sigterm; 8 | ignore @@ Unix.wait () 9 | -------------------------------------------------------------------------------- /test/sigterm/daemon.ml: -------------------------------------------------------------------------------- 1 | (** Test program for the bisect's signal handler. 2 | 3 | It forks: the parent process loops indefinitively, 4 | until the child process kills the parent with 5 | sigterm. 6 | 7 | If bisect has installed a signal handler we should see 8 | two coverage files: one from the parents sig handler and one 9 | from the childs at_exit hook. 10 | *) 11 | let () = 12 | let parent = Unix.getpid () in 13 | match Unix.fork () with 14 | | 0 -> Unix.kill parent Sys.sigterm 15 | | _ -> while true do Unix.sleep 5 done 16 | -------------------------------------------------------------------------------- /test/sigterm/dune: -------------------------------------------------------------------------------- 1 | (cram 2 | (deps daemon.ml at_exit_main.ml at_exit_hook.ml)) 3 | -------------------------------------------------------------------------------- /test/sigterm/sigterm.t: -------------------------------------------------------------------------------- 1 | Bisect's runtime can install a signal handler. 2 | 3 | $ echo "(lang dune 2.7)" > dune-project 4 | $ cat > dune <<'EOF' 5 | > (executables 6 | > (names daemon) 7 | > (preprocess (pps bisect_ppx --bisect-sigterm))) 8 | > EOF 9 | $ dune exec ./daemon.exe 10 | $ ls bisect*.coverage | wc -l | sed 's/ *//' 11 | 2 12 | $ bisect-ppx-report summary --verbose 13 | Info: found *.coverage files in './' 14 | Coverage: 5/6 (83.33%) 15 | $ rm bisect*.coverage 16 | 17 | An application instrumented with a signal handler will write coverage 18 | data when terminating normally: 19 | 20 | $ echo "(lang dune 2.7)" > dune-project 21 | $ cat > dune <<'EOF' 22 | > (executables 23 | > (names normal) 24 | > (preprocess (pps bisect_ppx --bisect-sigterm))) 25 | > EOF 26 | $ cat > normal.ml <<'EOF' 27 | > let () = () 28 | > EOF 29 | $ dune exec ./normal.exe 30 | $ bisect-ppx-report summary --verbose 31 | Info: found *.coverage files in './' 32 | Coverage: 0/0 (100.00%) 33 | $ rm bisect*.coverage 34 | -------------------------------------------------------------------------------- /test/sigterm/sigterm_atexit.t: -------------------------------------------------------------------------------- 1 | Signal handlers are restored after dumping coverage, in the case that 2 | bisect runs before an applications at_exit hook. 3 | 4 | In this test, we instrument an application `at_exit_main.ml` that has 5 | it's own at_exit hook. We ensure that bisect's hook is running before 6 | the applications hook. To do so, we put the applications hook in a 7 | separate file `at_exit_hook.ml` with coverage turned off: this means 8 | that bisect's instrumentation won't kick in until after the 9 | `at_exit.ml` has added the applications hook. 10 | 11 | The applications forks. The child will immediately exit and then sleep 12 | a few seconds in its at_exit hook. If allowed to sleep uninterrupted, 13 | it outputs a message. However, the parent kills the child before it 14 | has time to do so. If bisect has properly restored the default signal 15 | handler, we should see no message and find two coverage traces: 16 | 17 | $ echo "(lang dune 2.7)" > dune-project 18 | $ cat > dune <<'EOF' 19 | > (executable 20 | > (name at_exit_main) 21 | > (preprocess (pps bisect_ppx --bisect-sigterm))) 22 | > EOF 23 | $ dune exec ./at_exit_main.exe 24 | $ ls bisect*.coverage | wc -l | sed 's/ *//' 25 | 2 26 | $ bisect-ppx-report summary --verbose 27 | Info: found *.coverage files in './' 28 | Coverage: 6/6 (100.00%) 29 | -------------------------------------------------------------------------------- /test/usage/dune-conditional/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : test 2 | test : clean 3 | dune exec ./source.exe 4 | ! test -f bisect*.coverage 5 | dune clean 6 | BISECT_ENABLE=YES dune exec ./source.exe 7 | ls -l _build 8 | test -f bisect*.coverage 9 | 10 | .PHONY : clean 11 | clean : 12 | dune clean 13 | rm -f bisect*.coverage 14 | -------------------------------------------------------------------------------- /test/usage/dune-conditional/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name source) 3 | (preprocess (pps bisect_ppx --conditional))) 4 | -------------------------------------------------------------------------------- /test/usage/dune-conditional/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.0) 2 | -------------------------------------------------------------------------------- /test/usage/dune-conditional/dune-workspace: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aantron/bisect_ppx/dc29402f2f8b6246150b18ac2e7e1d48e578a106/test/usage/dune-conditional/dune-workspace -------------------------------------------------------------------------------- /test/usage/dune-conditional/source.ml: -------------------------------------------------------------------------------- 1 | let () = () 2 | -------------------------------------------------------------------------------- /test/usage/dune-integration/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : test 2 | test : clean 3 | dune exec ./source.exe 4 | ! test -f bisect*.coverage 5 | dune clean 6 | dune exec --instrument-with bisect_ppx ./source.exe 7 | ls -l _build 8 | test -f bisect*.coverage 9 | 10 | .PHONY : clean 11 | clean : 12 | dune clean 13 | rm -f bisect*.coverage 14 | -------------------------------------------------------------------------------- /test/usage/dune-integration/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name source) 3 | (instrumentation (backend bisect_ppx))) 4 | -------------------------------------------------------------------------------- /test/usage/dune-integration/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.7) 2 | -------------------------------------------------------------------------------- /test/usage/dune-integration/dune-workspace: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aantron/bisect_ppx/dc29402f2f8b6246150b18ac2e7e1d48e578a106/test/usage/dune-integration/dune-workspace -------------------------------------------------------------------------------- /test/usage/dune-integration/source.ml: -------------------------------------------------------------------------------- 1 | let () = () 2 | -------------------------------------------------------------------------------- /test/usage/dune-linkall/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : test 2 | test : 3 | dune exec ./source.exe 4 | ! test -f bisect*.coverage 5 | 6 | .PHONY : clean 7 | clean : 8 | dune clean 9 | rm -f bisect*.coverage 10 | -------------------------------------------------------------------------------- /test/usage/dune-linkall/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name source) 3 | (flags (-linkall)) 4 | (preprocess (pps bisect_ppx --conditional))) 5 | -------------------------------------------------------------------------------- /test/usage/dune-linkall/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.0) 2 | -------------------------------------------------------------------------------- /test/usage/dune-linkall/dune-workspace: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aantron/bisect_ppx/dc29402f2f8b6246150b18ac2e7e1d48e578a106/test/usage/dune-linkall/dune-workspace -------------------------------------------------------------------------------- /test/usage/dune-linkall/source.ml: -------------------------------------------------------------------------------- 1 | let () = () 2 | -------------------------------------------------------------------------------- /test/usage/dune-unconditional/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : test 2 | test : clean 3 | dune exec ./source.exe 4 | test -f bisect*.coverage 5 | 6 | .PHONY : clean 7 | clean : 8 | dune clean 9 | rm -f bisect*.coverage 10 | -------------------------------------------------------------------------------- /test/usage/dune-unconditional/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name source) 3 | (preprocess (pps bisect_ppx))) 4 | -------------------------------------------------------------------------------- /test/usage/dune-unconditional/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.0) 2 | -------------------------------------------------------------------------------- /test/usage/dune-unconditional/dune-workspace: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aantron/bisect_ppx/dc29402f2f8b6246150b18ac2e7e1d48e578a106/test/usage/dune-unconditional/dune-workspace -------------------------------------------------------------------------------- /test/usage/dune-unconditional/source.ml: -------------------------------------------------------------------------------- 1 | let () = () 2 | -------------------------------------------------------------------------------- /test/usage/js_of_ocaml/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : test 2 | test : clean 3 | dune build ./source.bc.js 4 | node _build/default/source.bc.js 5 | ! test -f bisect*.coverage 6 | dune clean 7 | BISECT_ENABLE=YES dune build ./source.bc.js 8 | node _build/default/source.bc.js 9 | ls -l _build 10 | test -f bisect*.coverage 11 | 12 | .PHONY : clean 13 | clean : 14 | dune clean 15 | rm -f bisect*.coverage 16 | -------------------------------------------------------------------------------- /test/usage/js_of_ocaml/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name source) 3 | (preprocess (pps bisect_ppx --conditional))) 4 | -------------------------------------------------------------------------------- /test/usage/js_of_ocaml/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.0) 2 | -------------------------------------------------------------------------------- /test/usage/js_of_ocaml/dune-workspace: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aantron/bisect_ppx/dc29402f2f8b6246150b18ac2e7e1d48e578a106/test/usage/js_of_ocaml/dune-workspace -------------------------------------------------------------------------------- /test/usage/js_of_ocaml/source.ml: -------------------------------------------------------------------------------- 1 | let () = 2 | Bisect.Runtime.write_coverage_data () 3 | -------------------------------------------------------------------------------- /test/usage/js_of_ocaml_with_runtime_library/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : test 2 | test : clean 3 | dune build ./source.bc.js 4 | node _build/default/source.bc.js 5 | ! test -f bisect*.coverage 6 | dune clean 7 | BISECT_ENABLE=YES dune build ./source.bc.js 8 | node _build/default/source.bc.js 9 | ls -l _build 10 | test -f bisect*.coverage 11 | 12 | .PHONY : clean 13 | clean : 14 | dune clean 15 | rm -f bisect*.coverage 16 | -------------------------------------------------------------------------------- /test/usage/js_of_ocaml_with_runtime_library/dune: -------------------------------------------------------------------------------- 1 | ; This is identical to the test in test/usage/js_of_ocaml, except that it links 2 | ; in the js_of_ocaml runtime library. This changes the type of exception that's 3 | ; raised when attempting to write coverage data in Runtime.dump from Failure to 4 | ; Js.Error, since Failure is only used by caml_wrap_exception when Js.Error is 5 | ; not present. 6 | (executable 7 | (name source) 8 | (libraries js_of_ocaml-compiler.runtime) 9 | (preprocess (pps bisect_ppx --conditional))) 10 | -------------------------------------------------------------------------------- /test/usage/js_of_ocaml_with_runtime_library/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.0) 2 | -------------------------------------------------------------------------------- /test/usage/js_of_ocaml_with_runtime_library/dune-workspace: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aantron/bisect_ppx/dc29402f2f8b6246150b18ac2e7e1d48e578a106/test/usage/js_of_ocaml_with_runtime_library/dune-workspace -------------------------------------------------------------------------------- /test/usage/js_of_ocaml_with_runtime_library/source.ml: -------------------------------------------------------------------------------- 1 | let () = 2 | Bisect.Runtime.write_coverage_data () 3 | -------------------------------------------------------------------------------- /test/usage/ocamlfind/Makefile: -------------------------------------------------------------------------------- 1 | BISECT := -package bisect_ppx -ppxopt bisect_ppx,--conditional 2 | 3 | .PHONY : test 4 | test : clean 5 | ocamlfind opt -linkpkg source.ml 6 | ./a.out 7 | ! test -f bisect*.coverage 8 | @# If the PPX option is passed correctly, there will be no output. 9 | ocamlfind opt -linkpkg $(BISECT) source.ml 10 | ./a.out 11 | ! test -f bisect*.coverage 12 | BISECT_ENABLE=YES ocamlfind opt -linkpkg $(BISECT) source.ml 13 | ./a.out 14 | test -f bisect*.coverage 15 | 16 | .PHONY : clean 17 | clean : 18 | rm -f *.cm* *.o a.out bisect*.coverage 19 | -------------------------------------------------------------------------------- /test/usage/ocamlfind/source.ml: -------------------------------------------------------------------------------- 1 | let () = () 2 | -------------------------------------------------------------------------------- /test/usage/reason/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : test 2 | test : clean 3 | dune exec ./source.exe 4 | ! test -f bisect*.coverage 5 | dune clean 6 | BISECT_ENABLE=YES dune exec ./source.exe 7 | ls -l _build 8 | test -f bisect*.coverage 9 | bisect-ppx-report html 10 | 11 | .PHONY : clean 12 | clean : 13 | dune clean 14 | rm -rf bisect*.coverage _coverage 15 | -------------------------------------------------------------------------------- /test/usage/reason/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name source) 3 | (preprocess (pps bisect_ppx --conditional))) 4 | -------------------------------------------------------------------------------- /test/usage/reason/dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.0) 2 | -------------------------------------------------------------------------------- /test/usage/reason/dune-workspace: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aantron/bisect_ppx/dc29402f2f8b6246150b18ac2e7e1d48e578a106/test/usage/reason/dune-workspace -------------------------------------------------------------------------------- /test/usage/reason/source.re: -------------------------------------------------------------------------------- 1 | let () = (); 2 | --------------------------------------------------------------------------------