├── .github ├── dependabot.yml └── workflows │ ├── main.yml │ └── security_audit.yml ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE.Apache2 ├── LICENSE.MIT ├── README.md ├── docs ├── architecture.md ├── beyond-02.md ├── customization.md ├── design-decisions.md ├── how-to-implement-a-mutator.md ├── mutators.md ├── test-structure.md └── vision.md ├── examples ├── feature-gated │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── simple │ ├── Cargo.toml │ └── src │ │ ├── bubblesort.rs │ │ ├── ggt.rs │ │ ├── lazy_add.rs │ │ ├── lib.rs │ │ ├── not_covered.rs │ │ ├── primetest.rs │ │ └── simple_add.rs ├── with-integration-tests-v1 │ ├── Cargo.toml │ ├── src │ │ └── lib.rs │ └── tests │ │ └── test_fib.rs └── with-integration-tests-v2 │ ├── Cargo.toml │ ├── src │ └── lib.rs │ └── tests │ └── test_fib.rs ├── mutagen-core ├── Cargo.toml └── src │ ├── comm.rs │ ├── comm │ ├── coverage.rs │ ├── mutagen_files.rs │ ├── mutation.rs │ └── report.rs │ ├── lib.rs │ ├── mutator.rs │ ├── mutator │ ├── mutator_binop_bit.rs │ ├── mutator_binop_bool.rs │ ├── mutator_binop_cmp.rs │ ├── mutator_binop_eq.rs │ ├── mutator_binop_num.rs │ ├── mutator_lit_bool.rs │ ├── mutator_lit_int.rs │ ├── mutator_lit_str.rs │ ├── mutator_stmt_call.rs │ └── mutator_unop_not.rs │ ├── runtime_config.rs │ ├── transformer.rs │ └── transformer │ ├── arg_ast.rs │ ├── ast_inspect.rs │ ├── mutate_args.rs │ ├── transform_context.rs │ └── transform_info.rs ├── mutagen-runner ├── Cargo.toml └── src │ ├── lib.rs │ ├── main.rs │ ├── progress.rs │ ├── progress_bar.rs │ └── test_bin.rs ├── mutagen-selftest ├── Cargo.toml └── src │ ├── lib.rs │ ├── mutator.rs │ ├── mutator │ ├── test_binop_bit.rs │ ├── test_binop_bool.rs │ ├── test_binop_cmp.rs │ ├── test_binop_eq.rs │ ├── test_binop_num.rs │ ├── test_lit_bool.rs │ ├── test_lit_int.rs │ ├── test_lit_str.rs │ ├── test_stmt_call.rs │ └── test_unop_not.rs │ ├── runtime_config.rs │ └── test_not_mutated.rs ├── mutagen-transform ├── Cargo.toml └── src │ └── lib.rs └── mutagen ├── Cargo.toml └── src └── lib.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # Base CI runs on nightly 2 | 3 | name: Mutagen Continous Intregation 4 | on: [push, pull_request] 5 | env: 6 | CARGO_TERM_COLOR: always 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions-rs/toolchain@v1 14 | with: 15 | toolchain: nightly 16 | override: true 17 | # now test only passes on nightly rust 18 | - run: cargo +nightly test 19 | 20 | # Format the code with $ rust fmt --all -- --check 21 | 22 | fmt: 23 | name: Code format 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v3 27 | - uses: actions-rs/toolchain@v1 28 | with: 29 | toolchain: nightly 30 | override: true 31 | components: rustfmt 32 | - uses: actions-rs/cargo@v1 33 | with: 34 | command: fmt 35 | args: --all -- --check 36 | 37 | # Run rust clippy 38 | 39 | clippy: 40 | name: rust clippy 41 | permissions: write-all 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v3 45 | - uses: actions-rs/toolchain@v1 46 | with: 47 | components: clippy 48 | toolchain: nightly 49 | override: true 50 | - uses: actions-rs/clippy-check@v1 51 | with: 52 | token: ${{ secrets.GITHUB_TOKEN }} 53 | args: --all-features 54 | -------------------------------------------------------------------------------- /.github/workflows/security_audit.yml: -------------------------------------------------------------------------------- 1 | name: Security Audit 2 | 3 | on: 4 | schedule: 5 | # Runs at 00:00 UTC everyday 6 | - cron: '0 0 * * *' 7 | push: 8 | paths: 9 | - '**/Cargo.toml' 10 | - '**/Cargo.lock' 11 | pull_request: 12 | 13 | jobs: 14 | security_audit: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v3 19 | - name: Install Rust 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | toolchain: nightly 23 | profile: minimal 24 | override: true 25 | - uses: actions-rs/audit-check@v1.2.0 26 | with: 27 | token: ${{ secrets.GITHUB_TOKEN }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | /plugin/target/ 5 | /runner/target/ 6 | 7 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 8 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 9 | Cargo.lock 10 | 11 | # These are backup files generated by rustfmt 12 | **/*.rs.bk 13 | 14 | # Generated file from integration tests 15 | **/mutations 16 | 17 | .vscode/ 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: nightly 3 | os: 4 | - linux 5 | - osx 6 | cache: cargo 7 | sudo: false 8 | script: 9 | - cargo build 10 | - cargo test -p mutagen-core # run unit tests first 11 | - cargo test -p mutagen-selftest # run integration tests 12 | - cargo test # run all (other) tests 13 | 14 | # run executable cargo-mutagen on test project 15 | - pushd examples/simple 16 | - cargo run --package cargo-mutagen 17 | - popd 18 | - pushd examples/feature-gated 19 | - cargo run --package cargo-mutagen -- --features with_mutagen 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to mutagen 2 | 3 | Hi there! So you want to join the mutagen project or at least know what it entails? Welcome and read on! 4 | 5 | ## Getting started 6 | 7 | High level approach: 8 | 9 | 1. Find something to fix/improve 10 | 2. Change code, tests, docs, whatever 11 | 3. If you changed code, run `cargo test` (once we have a test suite, that is) both in the `mutagen/` dir and the `plugin/` subdirectory. 12 | If you cannot get the code to build / tests to pass, don't fret, just mark your contribution as work in progress ("WIP"). 13 | 4. Open a pull request. For that you need to fork the repository. See [GitHub's help page](https://help.github.com/articles/fork-a-repo/) 14 | for more information. 15 | 16 | ### Finding something to fix/improve 17 | 18 | All issues on mutagen are mentored, if you want help with an issue just ask @llogiq. 19 | 20 | Some issues are easier than others. The [good first issue](https://github.com/llogiq/mutagen/labels/good%20first%20issue) 21 | label can be used to find the easy issues. If you want to work on an issue, please leave a comment to have it assigned 22 | to you. 23 | 24 | ### Writing code, tests, docs, etc. 25 | 26 | All contributions are equally welcome, whether it's refactorings for a more readable codebase, fixing a bug or implementing 27 | new functionality, adding tests, documentation or setting up or improving CI, we'll be happy to have your contributions. 28 | 29 | ### Getting your contribution into mutagen 30 | 31 | Contributions to mutagen should be made in the form of GitHub pull requests. Each pull request will 32 | be reviewed by a core contributor (someone with permission to land patches) and either landed in the 33 | main tree or given feedback for changes that would be required. 34 | 35 | All code in this repository is under either the [Apache License 2.0](LICENSE.Apache2) or the [MIT License](LICENSE.MIT). 36 | 37 | ## Conduct 38 | 39 | We follow the [Rust Code of Conduct](http://www.rust-lang.org/conduct.html). 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "mutagen", 4 | "mutagen-core", 5 | "mutagen-runner", 6 | "mutagen-transform", 7 | "mutagen-selftest", 8 | "examples/simple", 9 | "examples/feature-gated", 10 | "examples/with-integration-tests-v1", 11 | "examples/with-integration-tests-v2", 12 | ] 13 | -------------------------------------------------------------------------------- /LICENSE.MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Breaking your Rust code for fun & profit 2 | 3 | *this is an architecture-preview, not all components are there* 4 | 5 | This is a mutation testing framework for Rust code. 6 | 7 | ## Mutation Testing 8 | 9 | A change (mutation) in the program source code is most likely a bug of some kind. A good test suite can detect changes in the source code by failing ("killing" the mutant). If the test suite is green even if the program is mutated (the mutant survives), the tests fail to detect this bug. 10 | 11 | Mutation testing is a way of evaluating the quality of a test suite, similar to code coverage. 12 | The difference to line or branch coverage is that those measure if the code under test was *executed*, but that says nothing about whether the tests would have caught any error. 13 | 14 | ## How `mutagen` works 15 | 16 | `mutagen`'s core functionality is implemented via a procedural macro that transforms the source code. Known patterns of code are replaced by mutators with identical behavior unless activated. Activating a mutator at runtime alters its behavior - having the effect of a mutation. 17 | 18 | The procedural macro has access to the bare AST. Information about inferred types, implemented traits, control flow, data flow or signatures of other functions are not available during the execution of procedural macros. Therefore, the mutations must be possible without additional type-information. 19 | 20 | In order to be fast, it is necessary that the compilation of the test suite is performed only once. To achieve this, all mutations are baked into the code once and are selected at runtime via an environment variable. This means the mutations are not allowed to produce code that fails to compile. 21 | 22 | This project is basically an experiment to see what mutations we can still apply under those constraints. 23 | 24 | ## Using mutagen 25 | 26 | **Note**: The version of `mutagen` (`0.2.0`) referenced in this README is not yet released on `crates.io`. To install and use an earlier, released version, you can follow the instructions on [crates.io mutagen crate](https://crates.io/crates/mutagen). 27 | 28 | You need Rust nightly to compile the procedural macro. 29 | 30 | Add the library `mutagen` as a `dev-dependency` to your `Cargo.toml` referencing this git repository: 31 | 32 | ```rust 33 | [dev-dependencies] 34 | mutagen = {git = "https://github.com/llogiq/mutagen"} 35 | ``` 36 | 37 | To use the attribute `#[mutate]`, you need to import it. 38 | 39 | ```rust 40 | #[cfg(test)] 41 | use mutagen::mutate; 42 | ``` 43 | 44 | Now you can advise `mutagen` to mutate any function or method by prepending `#[cfg_attr(test, mutate)]`. The use of `cfg_attr` ensures the `#[mutate]` attribute will only be active in test mode. The repository contains an example that shows how `mutagen` could be used. 45 | 46 | ### Running mutagen 47 | 48 | Install `cargo-mutagen`, which can be done by running `cargo install cargo-mutagen`. Run `cargo mutagen` on the project under test for a complete mutation test evaluation. 49 | 50 | The mutants can also be run manually: `cargo test` will compile code and write the performed mutations to `target/mutagen/mutations`. This file contains ids and descriptions of possible mutations. 51 | Then, the environment variable `MUTATION_ID` can be used to activate a single mutation as defined by the `mutations` file. The environment variable can be set before calling the test suite, i.e. `MUTATION_ID=1 cargo test`, `MUTATION_ID=2 ..`, etc. For every mutation count at of least one, the test suite should fail 52 | 53 | You can run `cargo mutagen -- --coverage` in order to reduce the time it takes to run the mutated code. When running in this mode, it runs the test suite at the beginning of the process and checks which tests are hitting mutated code. Then, for each mutation, instead of running the whole test suite again, it executes only the tests that are affected by the current mutation. This mode is especially useful when the test suite is slow or when the mutated code affects a little part of it. 54 | 55 | If you referenced `mutagen` in your cargo.toml via the git repository as noted in the `Using Mutagen` section, you will probably want to install the development version of `cargo-mutagen`. To install the development version, run `cargo install` in the `mutagen-runner` dir of this repository. Running `cargo install --force` might be necessary to overwrite any existing `cargo-mutagen` binary. 56 | 57 | ## A Word of Warning 58 | 59 | `mutagen` will change the code you annotate with the `#[mutate]` attribute. This can have dire consequences in some cases. However, functions not annotated with `#[mutate]` will not be altered. 60 | 61 | *Do not use `#[mutate]` for code that can cause damage if buggy*. By corrupting the behavior or sanity checks of some parts of the program, dangerous accidents can happen. For example by overwriting the wrong file or sending credentials to the wrong server. 62 | 63 | *Use `#[mutate]` for tests only.* This is done by always annotating functions or modules with `#[cfg_attr(test, mutate)]` instead, which applies the `#[mutate]` annotation only in `test` mode. If a function is annotated with plain `#[mutate]` in every mode, the mutation-code is baked into the code even when compiled for release versions. However, when using `mutagen` as `dev-dependency`, adding a plain `#[mutate]` attribute will result in compilation errors in non-test mode since the compiler does not find the annotation. 64 | 65 | *Use `mutagen` as `dev-dependency`, unless otherwise necessary.* This ensures that no code from `mutagen` is part of your released library or application. 66 | 67 | ## Limitations of Mutations 68 | 69 | *No mutations will be introduced in `unsafe`-blocks and `unsafe` functions*. Such mutations would probably break some invariants. Moreover, mutations in unsafe code could lead to undefined behavior that cannot be observed by any testcase. 70 | 71 | *`const` and `static` expressions cannot be mutated.* They are evaluated at compile-time and `mutagen` can only affect code that can alter its behavior at run-time. Array lengths and global constants are examples of `const` expressions. 72 | 73 | *Patterns cannot be mutated.* Mutations are introduced by injecting calls to mutagen-internal functions, which cannot be placed inside patterns. 74 | 75 | ## Contributing 76 | 77 | Issues and PRs welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) on how to help. 78 | -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | # Mutagen Architecture 2 | 3 | In `mutagen`, there is a difference between of `mutators` and `mutations`. 4 | 5 | 1. Mutators are inserted at compile-time by the attribute `#[mutate]` 6 | 2. Mutations are introduced at run-time by activating mutators 7 | 8 | ## Mutators 9 | 10 | Mutators are hooks inside the source code can be activated to change the behavior, they do not change the behavior of the code, unless activated. Each mutator is responsible for a single piece in the original code and can be activated for possibly multiple mutations. 11 | 12 | ### Procedural Macro `#[mutate]` 13 | 14 | The attribute `#[mutate]` is a procedural macro that inserts into the program by transforming the source code on the AST-level. 15 | 16 | Known patterns of code (e.g. arithmetic operations, boolean logic, loops, ...) get replaced by mutators. The code that describes the transformations to insert each mutator is implemented in the same file with the logic of that mutator. 17 | 18 | Each mutator only has access to the plain AST without inferred types or global information. The transformed code must successfully compile regardless of other global information. Unfortunately, the possibilities for performing type-analysis of the code under test is limited since the macro has no access to the code outside the annotated region. 19 | 20 | The test suite is compiled once and contains all mutators. 21 | 22 | ## Mutations 23 | 24 | A mutation occurs when a mutator is activated for a single run of the test suite. By default no mutator is active and calling `cargo test` should run all tests successfully. 25 | 26 | A mutator can be activated by setting the environment variable `MUTATION_ID` to a positive number for a single test suite run (e.g. `MUTATION_ID=1 cargo test`). Note that the test suite is not recompiled for each call of `cargo test` if the source code was not changed. 27 | 28 | If all tests pass despite the mutation, the mutant "survives". Otherwise, the mutant is "killed". The mutation coverage is the number of mutants that survived. 29 | 30 | ### Runtime Configuration 31 | 32 | The library `mutagen` defines a type `MutagenRuntimeConfig`, which contains the information about which mutation is activated for the current execution of the test suite by querying the `MUTATION_ID` environment variable. The global default config is fetched using the function `MutagenRuntimeConfig::get_default()`, which is already inserted into the source code by the `#[mutate]` attribute. 33 | 34 | ## Optimistic Mutations 35 | 36 | A standard mutation represents a change in the source code such that the changed source code still successfully compiles. A mutation is *optimistic* if the corresponding changed code does not compile when some type-level restrictions are not met. 37 | 38 | Since `mutagen` only compiles the test code only once and the procedural macro has no information for the types, mutators with optimistic mutations have to be inserted without the possibility to check if type-level assumptions hold. 39 | 40 | If the assumptions on the type are not fulfilled, the mutator panics in order to fail the test suite, since it is not desirable to count such mutants as survivors since they do not represent a valid alteration of the source code. To implement this behavior, the unstable feature `specialization` is used. 41 | 42 | Below, there are some examples of optimistic mutators and their type-level assumptions. 43 | 44 | ### Mutations on arithmetic 45 | 46 | Rust's type system allows to write a custom implementation for the operators `+` and `-` independently from each other. In general, if one operator is implemented, the other might not be implemented or the required types differ. However in many contexts, both operators are indeed defined and replacing one by the other would lead to a bug without producing compiler errors. 47 | 48 | ### Removing negation 49 | 50 | Most types that have a implementation for `Not` have `Output = Self`. This is true for the logic and numerical types. Removing the negation will be a valid mutation in most cases. 51 | -------------------------------------------------------------------------------- /docs/beyond-02.md: -------------------------------------------------------------------------------- 1 | # Beyond mutagen-0.2 2 | 3 | The version 0.2 of mutagen is a complete rewrite of the framework while staying true to its original goals (see [Vision Document](./vision.md)). 4 | 5 | ## Status Quo 6 | 7 | In mutagen-0.2, core features are implemented, which makes basic use of mutation testing in Rust possible. 8 | 9 | * Basic set of mutations: Arithmetic and logical operations, statement deletion 10 | * running the mutation-analysis via cargo-plugin 11 | * text based report 12 | 13 | ## Community Feedback 14 | 15 | At time of writing, feedback of the community has not been collected. 16 | 17 | ## Planned Features 18 | 19 | ### Web Report 20 | 21 | A Web-based report is capable of displaying more information that cannot be presented in the textual report. It is planned to add some form of Web-based report to display mutation testing results, which can give a better overview but also display the information about the quality of the test suite in finer detail. Most other mutation testing frameworks for other languages provide similar functionality. 22 | 23 | ### Testcase Tracking and Skipping 24 | 25 | Ideally, only the tests that cover the activated mutation are executed. However, this requires injecting a dynamic check into each unit test that allows to skip tests irrelevant to this mutation, which requires a new procedural macro. The technical limitations have not yet been studied, but this approach seems promising for improving performance of mutation analysis. 26 | 27 | ### Additional Mutators 28 | 29 | The list of implemented mutators in mutagen-0.2 is a minimal set to make mutagen useful. In the next releases, further code patterns are planned to be mutated. 30 | 31 | * loops 32 | * string literals 33 | * if conditions 34 | * control flow: return early from functions, break early from loops, ... 35 | 36 | ### Customization 37 | 38 | In mutagen-0.2, customization of mutators is not supported. We will engage with the community to determine what customizations are requested and considered useful. 39 | -------------------------------------------------------------------------------- /docs/customization.md: -------------------------------------------------------------------------------- 1 | # Customization of `mutagen` 2 | 3 | The behavior of `mutagen` and the attribute `#[mutate]` can be customized by adding arguments. 4 | 5 | ## Configuring the list of mutators 6 | 7 | The list of active mutators for a function to be run can be specified by adding arguments `mutators = only(...)` and `not(...)`. In both cases, a list of mutators is required inside the brackets. 8 | 9 | The details of all mutators are described in their own folder (see: [overview](mutators)). 10 | 11 | ### Examples 12 | 13 | ```rust 14 | // only mutate int-literals 15 | #[mutate(mutators = only(lit_int))] 16 | 17 | // only mutate int-literals and `+` operations. 18 | #[mutate(mutators = only(lit_int, binop_num))] 19 | 20 | // include all mutations except bool literal mutations 21 | #[mutate(mutators = not(lit_bool))] 22 | ``` 23 | 24 | ## WIP: arguments for mutators 25 | 26 | Will probably look like this: some mutators have arguments, given after the list of mutators 27 | 28 | ``` 29 | #[mutate(not(early_return), lit_int(+1, =0))] 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/design-decisions.md: -------------------------------------------------------------------------------- 1 | # Design Decisions 2 | 3 | Several high-level decision have lead to the current design of `mutagen`. 4 | 5 | ## Opt-in 6 | 7 | Mutagen is implemented as a procedural macro, which means that only code annotated with `#[mutate]` is considered for mutations. This is a limitation but also a great feature (see warnings in the `readme`). 8 | 9 | ## Compile-once 10 | 11 | This library is designed to be fast. We cannot afford re-compiling the test suite for every mutation. This means that all mutations have to be baked in at compile-time. This means we must avoid mutations that break the code in a way that it no longer compiles. 12 | 13 | ## Procedural Macro 14 | 15 | The functionality to inject mutators into the source code is implemented with a procedural macro. Since the stabilization of the [`proc_macro`](https://doc.rust-lang.org/stable/proc_macro/index.html) crate and the development of the libraries [`proc-macro2`](https://crates.io/crates/proc-macro2), [`syn`](https://crates.io/crates/syn) and [`quote`](https://crates.io/crates/quote) writing procedural macros has become more popular and a lot easier. 16 | 17 | The input of the procedural macro is the parsed source that was annotated with the attribute `#[mutate]`. It does not have access to any information about inferred types, implemented traits and signatures of other functions. Since the test-suite is compiled only once, no mutator is allowed to produce code that fails to compile. This restricts the possible mutations. 18 | 19 | ## Customization 20 | 21 | It should be possible to customize the list of mutators for each method. This is especially necessary in case some mutators leads to compile errors for some input. Omitting a single or few mutators is possible by giving a blacklist. Equivalently, a whitelist of mutators can be given. 22 | 23 | Users of `mutagen` should be able to customize some mutations. This is especially relevant for mutators that can produce a large number of mutations (like int literals) but only a few of them are selected by default. 24 | -------------------------------------------------------------------------------- /docs/how-to-implement-a-mutator.md: -------------------------------------------------------------------------------- 1 | # How to implement a Mutator 2 | 3 | To add a mutator the a few steps are required. 4 | 5 | ## Name the mutator 6 | 7 | Every mutator is named after the code pattern it affects, e.g. mutator `binop_bool` mutates binop-expressions containing the bool-operators `&&` or `||`. 8 | 9 | In the following the mutator's name will be denoted by `$name`. 10 | 11 | ## Implement the mutator 12 | 13 | The majority of the mutator's logic is implemented in the file `mutagen-core/src/mutator/mutator_$name.rs`. 14 | 15 | Each mutator consists of two functions: transformer and runner. 16 | 17 | ### Impement the transformer 18 | 19 | The transform-function should be named `transform` and placed at the top level of the file. Its signature is like `FnMut(Expr, &SharedTransformInfo, &TransformContext) -> Expr`. The `Expr` is replaced with the general ast-node that the mutator transforms. Currently, `Expr` and `Stmt` are used. 20 | 21 | If the input matches the mutator's pattern, the target code should be replaced with a call to the function `run`. How to call this function and its arguments depend on the mutator and the semantics of the original code. 22 | 23 | The given `transform_info` is used to register the inserted mutations and get the `mutator_id` to be used for the inserted `run`-function. 24 | 25 | ### Implement the runner 26 | 27 | The `run` method must take the `runtime_config` and the `mutator_id` as an argument. 28 | 29 | The first action in this function should be a call to `runtime_config.covered(mutator_id)` to signal that this mutator has been covered. 30 | 31 | If the `mutator_id` does not match the `mutation_id` of the runtime, the mutator should execute the code as originally written. Otherwise, the mutated code should be executed. If the mutator implements several mutations, the ids following the `mutator_id` are used to activate these mutations. 32 | 33 | ## Add the new mutator to to the known mutators 34 | 35 | A `pub mod` entry is required in the `mutators.rs` file. 36 | 37 | The mutator has to be registered in the `transformer.rs` file: 38 | * The mutator has to be added to the `mk_transformer` function 39 | * The mutator's name has to be added to the `all_transformers` function 40 | 41 | ## Add tests 42 | 43 | Unit tests should be written in the file `mutator_$name`. 44 | 45 | The crate `mutagen-selftest` contains integration tests. Each mutator has its own file `test_$name`. The mutator should tested in different situations. For each situation, the expected number of mutations should be given and a test should be written for each mutation-id. 46 | 47 | ## Document the mutator 48 | 49 | The file `docs/mutators.md` documents all mutators and their behavior: 50 | 51 | * the mutator's name 52 | * a description of the target code pattern 53 | * a list of possible mutations 54 | * optional: limitations of the mutator, i.e. which code does not get 55 | * optional: supported customization of the mutator 56 | -------------------------------------------------------------------------------- /docs/mutators.md: -------------------------------------------------------------------------------- 1 | # Implemented Mutators 2 | 3 | `mutagen` provides several mutators. The document gives a rough overview of the implemented mutators. 4 | 5 | ## lit_bool 6 | 7 | ### Target Code 8 | 9 | bool literals `true` and `false` 10 | 11 | ### Mutations 12 | 13 | 1. negating the value of the literal 14 | 15 | ## lit_int 16 | 17 | ### Target Code 18 | 19 | Integer literals like `0`, `1u8`, `5isize`. 20 | 21 | Byte-literals like `b'a'` are not mutated by this mutator. 22 | 23 | ### Mutations 24 | 25 | 1. replacing the literal with a different literal 26 | 27 | ### Limitations 28 | 29 | * literals cannot be mutated into negative numbers 30 | * literals with a value that does not fit into `u128` are not mutated 31 | 32 | ### Customization 33 | 34 | Customization is WIP 35 | 36 | ## lit_str 37 | 38 | ### Target Code 39 | 40 | `&str` literals. 41 | 42 | Char literals like `'a'` are not mutated by this mutator. 43 | 44 | ### Mutations 45 | 46 | * If not empty: 47 | * Replace the literal with an empty string 48 | * Prepend `'-'` 49 | * Append `'-'` 50 | * If empty: 51 | * Replace the literal with `"A"` 52 | 53 | ## unop_not 54 | 55 | ### Target Code 56 | 57 | `!`-expressions, like `!done` 58 | 59 | ### Mutations 60 | 61 | 1. removing the negation, i.e. replacing `!x` with `x` 62 | 63 | ### Limitations 64 | 65 | This is a optimistic mutator. For some types the output type of the negation may be too different from the input type. 66 | 67 | such that the input type cannot be converted to it via `Into` without calling the negation. 68 | 69 | ## binop_bool 70 | 71 | ### Target Code 72 | 73 | expressions containing `&&` and `||`. 74 | 75 | ### Mutations 76 | 77 | 1. replacing `&&` with `||` 78 | 2. replacing `||` with `&&` 79 | 80 | ### Limitations 81 | 82 | none. 83 | 84 | The target code has the same short-circuiting behavior to the original operators: When the right argument is not needed for the value of the mutated or original expression, the right argument is not evaluated. 85 | 86 | ## binop_cmp 87 | 88 | ### Target Code 89 | 90 | expressions that compare two values: 91 | 92 | * `x < y` 93 | * `x <= y` 94 | * `x >= y` 95 | * `x > y` 96 | 97 | ### Mutations 98 | 99 | 1. replacing the comparison with any of the other three 100 | 101 | ## binop_eq 102 | 103 | ### Target Code 104 | 105 | `==`-expressions, like `x == y` 106 | 107 | ### Mutations 108 | 109 | 1. replacing `==` with `!=` 110 | 2. replacing `!=` with `==` 111 | 112 | ## binop_num 113 | 114 | ### Target Code 115 | 116 | expressions of numeric operators `+`, `-`, `*`, `/`, like `a+y` 117 | 118 | ### Mutations 119 | 120 | 1. replace `+` with `-` 121 | 2. replace `-` with `+` 122 | 3. replace `*` with `/` 123 | 4. replace `/` with `*` 124 | 125 | ### Limitations 126 | 127 | This is a optimistic mutator. The trait corresponding to the operation might not be implemented for the types inside the mutated expression. 128 | 129 | ### Customization 130 | 131 | Customization is WIP 132 | 133 | Changing the any numeric operator to the other binary numeric operations as well as the bit-wise operations are possible optimistic mutations. 134 | 135 | ## binop_bit 136 | 137 | ### Target Code 138 | 139 | expressions of numeric operators `&`, `|`, `^`, like `x | 1` 140 | 141 | ### Mutations 142 | 143 | 1. the bit operation with any of the other two 144 | 145 | ### Limitations 146 | 147 | This is a optimistic mutator. The trait corresponding to the operation might not be implemented for the types inside the mutated expression. 148 | 149 | ## stmt_call 150 | 151 | ### Target Code 152 | 153 | Statements that call a single function or method 154 | 155 | ### Mutations 156 | 157 | 1. removing the call to the function or method 158 | 159 | ### Limitations 160 | 161 | This operation is optimistic, since the statement could have the type `!` and can be used in surprising contexts: 162 | 163 | * `let x = {f(return y);}` 164 | * `let x = {std::process::abort();}` 165 | 166 | Above examples compile and it is not possible to remove the statements without introducing compiler errors. 167 | -------------------------------------------------------------------------------- /docs/test-structure.md: -------------------------------------------------------------------------------- 1 | # Test Plan 2 | 3 | `mutagen`'s logic is tested using unit tests, integration tests and an example-crate. 4 | 5 | ## Unit tests of mutators 6 | 7 | The behavior of all mutators is tested using traditional unit tests in the corresponding modules. The mutators are run with run-time configurations constructed by the test itself. 8 | 9 | ## Tests of `#[mutate]` 10 | 11 | The behavior of the `#[mutate]` attribute for single mutators is tested inside the crate `mutagen-selftest`. 12 | 13 | ### Test Isolation 14 | 15 | Instead of having a program-wide unique id per mutation, each function can have their own local mutation ids starting from one by writing adding `conf = local` to the arguments of `#[mutate]`. This ensures that the mutation ids are independent for each test. Moreover, only one mutator is enabled in each test by adding `mutators = only(...)` to the mutagen arguments. 16 | 17 | ### Exhaustive Testing 18 | 19 | Each test sets the `mutation_id` for the single test run and runs test code. This enables exhaustive testing of all mutations within a single run of the test suite and without dependency on external environment variables. 20 | 21 | For every mutator, it is tested whether all its mutations actually produce the expected deviation from standard behavior and that they have no effect on the code unless activated. 22 | 23 | To ensure that the expected number of mutations are generated, the argument `expected_mutations` is added to the configuration. This includes a check that exactly that many mutations are generated by the function nuder test 24 | 25 | ### Implementation Details 26 | 27 | The crate `mutagen-selftest` has dependencies to `mutagen` and `mutagen-core` and uses the libraries similar to other creates. 28 | 29 | Setting the `mutation_id` during test is possible via special functions that mutate the global run-time configuration and are only available when enabling the feature `self_test` of `mutagen-core`. The feature `self_test` is not supposed to be used by users of `mutagen`. 30 | 31 | ### Example 32 | 33 | Typically, a complete test of some feature looks like this 34 | ```rust 35 | mod test_x { 36 | // only enable mutator `xyz` 37 | // assert that 2 mutations will be generated 38 | #[mutate(conf = local(expected_mutations = 2), mutators = only(xzy))] 39 | pub fn x() { 40 | // function to mutate 41 | } 42 | 43 | #[test] 44 | pub fn x_inactive() { 45 | test_without_mutation( || { 46 | // test and assert on `x()` where no mutations have been performed 47 | }) 48 | } 49 | #[test] 50 | pub fn x_active() { 51 | test_with_mutation_id(1, || { 52 | // test and assert that the correct mutation has been performed in `x()` 53 | }) 54 | } 55 | 56 | // more tests with other mutation ids, if more than one mutation has been performed 57 | } 58 | ``` 59 | 60 | ## Tests of an example crate 61 | 62 | With the crate `example/simple` of this repository, end-to-end tests of mutagen can be performed. The binary `cargo-mutagen` can be run on this crate to test the functionality of all parts of the system, including building a mutation report. 63 | 64 | The test can be executed with the command. 65 | 66 | ```bash 67 | cargo run -p cargo-mutagen 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/vision.md: -------------------------------------------------------------------------------- 1 | # Vision 2 | 3 | The vision of this project to provide a mutation testing framework, that: 4 | 5 | * mutated code has little overhead compared to the original code 6 | * is easy to add to existing projects 7 | * produces well-typed code on all valid codebases 8 | * is fast by only compiling the test suite once 9 | * calculates meaningful test coverage 10 | * only produces mutations that are representable in rust source code 11 | * provides useful feedback about the strength of test suite 12 | 13 | WIP: further discussion 14 | -------------------------------------------------------------------------------- /examples/feature-gated/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "feature-gated" 3 | version = "0.2.0" 4 | authors = ["Samuel Pilz "] 5 | edition = "2018" 6 | 7 | [dev-dependencies] 8 | mutagen = {path = "../../mutagen"} 9 | 10 | [lib] 11 | doctest = false 12 | 13 | [features] 14 | with_mutagen = [] 15 | 16 | -------------------------------------------------------------------------------- /examples/feature-gated/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg_attr(all(test, feature = "with_mutagen"), ::mutagen::mutate)] 2 | pub fn foo() -> i32 { 3 | 1 + 2 4 | } 5 | 6 | #[cfg(test)] 7 | mod tests { 8 | use super::foo; 9 | 10 | #[test] 11 | fn test_foo() { 12 | assert_eq!(foo(), 3); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/simple/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-simple" 3 | version = "0.2.0" 4 | authors = ["Samuel Pilz "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | mutagen = {path = "../../mutagen"} 9 | 10 | [lib] 11 | doctest = false 12 | -------------------------------------------------------------------------------- /examples/simple/src/bubblesort.rs: -------------------------------------------------------------------------------- 1 | use mutagen::mutate; 2 | 3 | #[cfg_attr(test, mutate)] 4 | pub fn bubblesort_for(arr: &mut [u8]) { 5 | let n = arr.len(); 6 | for _ in 1..n { 7 | for i in 1..n { 8 | if arr[i - 1] > arr[i] { 9 | arr.swap(i - 1, i); 10 | } 11 | } 12 | } 13 | } 14 | 15 | #[cfg_attr(test, mutate)] 16 | pub fn bubblesort_while(arr: &mut [u8]) { 17 | let n = arr.len(); 18 | let mut change = true; 19 | while change { 20 | change = false; 21 | for i in 1..n { 22 | if arr[i - 1] > arr[i] { 23 | arr.swap(i - 1, i); 24 | change = true; 25 | } 26 | } 27 | } 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | 33 | use super::*; 34 | 35 | #[test] 36 | fn test_bubblesort_for_123() { 37 | let mut arr = vec![1, 2, 3]; 38 | bubblesort_for(&mut arr); 39 | assert_eq!(&*arr, [1, 2, 3]); 40 | } 41 | #[test] 42 | fn test_bubblesort_for_321() { 43 | let mut arr = vec![3, 2, 1]; 44 | bubblesort_for(&mut arr); 45 | assert_eq!(&*arr, [1, 2, 3]); 46 | } 47 | 48 | #[test] 49 | fn test_bubblesort_while_123() { 50 | let mut arr = vec![1, 2, 3]; 51 | bubblesort_while(&mut arr); 52 | assert_eq!(&*arr, [1, 2, 3]); 53 | } 54 | #[test] 55 | fn test_bubblesort_while_321() { 56 | let mut arr = vec![3, 2, 1]; 57 | bubblesort_while(&mut arr); 58 | assert_eq!(&*arr, [1, 2, 3]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/simple/src/ggt.rs: -------------------------------------------------------------------------------- 1 | use mutagen::mutate; 2 | 3 | #[cfg_attr(test, mutate)] 4 | pub fn ggt_loop(mut a: u32, mut b: u32) -> u32 { 5 | loop { 6 | if a == 0 { 7 | return b; 8 | } 9 | if b == 0 { 10 | return a; 11 | } 12 | if a > b { 13 | a -= b; 14 | } else { 15 | b -= a; 16 | } 17 | } 18 | } 19 | 20 | #[cfg_attr(test, mutate)] 21 | pub fn ggt_rec(mut a: u32, mut b: u32) -> u32 { 22 | if a == b || a == 0 || b == 0 { 23 | return a | b; 24 | } 25 | if a == 1 || b == 1 { 26 | return 1; 27 | } 28 | if a > b { 29 | ggt_rec(a - b, b) 30 | } else { 31 | ggt_rec(a, b - a) 32 | } 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | 38 | use super::*; 39 | 40 | #[test] 41 | fn test_ggt_loop_4_4() { 42 | assert_eq!(ggt_loop(4, 4), 4) 43 | } 44 | #[test] 45 | fn test_ggt_loop_3_5() { 46 | assert_eq!(ggt_loop(3, 5), 1) 47 | } 48 | #[test] 49 | fn test_ggt_loop_5_3() { 50 | assert_eq!(ggt_loop(5, 3), 1) 51 | } 52 | 53 | #[test] 54 | fn test_ggt_rec_4_4() { 55 | assert_eq!(ggt_rec(4, 4), 4) 56 | } 57 | #[test] 58 | fn test_ggt_rec_3_5() { 59 | assert_eq!(ggt_rec(3, 5), 1) 60 | } 61 | #[test] 62 | fn test_ggt_rec_5_3() { 63 | assert_eq!(ggt_rec(5, 3), 1) 64 | } 65 | #[test] 66 | fn test_ggt_rec_0_2() { 67 | assert_eq!(ggt_rec(0, 2), 2) 68 | } 69 | #[test] 70 | fn test_ggt_rec_2_0() { 71 | assert_eq!(ggt_rec(2, 0), 2) 72 | } 73 | #[test] 74 | fn test_ggt_rec_2_4() { 75 | assert_eq!(ggt_rec(2, 4), 2) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /examples/simple/src/lazy_add.rs: -------------------------------------------------------------------------------- 1 | use mutagen::mutate; 2 | 3 | enum LazyAdd { 4 | Val(u8), 5 | Lazy(Box, Box), 6 | } 7 | 8 | impl From for LazyAdd { 9 | fn from(v: u8) -> Self { 10 | Self::Val(v) 11 | } 12 | } 13 | 14 | impl std::ops::Add for LazyAdd { 15 | type Output = LazyAdd; 16 | fn add(self, rhs: LazyAdd) -> LazyAdd { 17 | LazyAdd::Lazy(Box::new(self), Box::new(rhs)) 18 | } 19 | } 20 | 21 | #[cfg_attr(test, mutate)] 22 | impl LazyAdd { 23 | pub fn eval(self) -> u8 { 24 | match self { 25 | Self::Val(v) => v, 26 | Self::Lazy(l, r) => l.eval() + r.eval(), 27 | } 28 | } 29 | 30 | pub fn add_one(self) -> Self { 31 | self + 1.into() 32 | } 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | 38 | use super::*; 39 | 40 | #[test] 41 | fn add_one_to_zero() { 42 | assert_eq!(LazyAdd::from(0).add_one().eval(), 1); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/simple/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused, clippy::no_effect)] 2 | 3 | mod bubblesort; 4 | mod ggt; 5 | mod lazy_add; 6 | mod not_covered; 7 | mod primetest; 8 | mod simple_add; 9 | -------------------------------------------------------------------------------- /examples/simple/src/not_covered.rs: -------------------------------------------------------------------------------- 1 | use mutagen::mutate; 2 | 3 | #[cfg_attr(test, mutate)] 4 | pub fn simple_assert_not_covered() { 5 | 1 < 3; 6 | } 7 | 8 | // There are no tests since the function above is supposed to be no covered by tests 9 | -------------------------------------------------------------------------------- /examples/simple/src/primetest.rs: -------------------------------------------------------------------------------- 1 | use mutagen::mutate; 2 | 3 | #[cfg_attr(test, mutate)] 4 | pub fn primetest(n: u32) -> bool { 5 | if n % 2 == 0u32 { 6 | return n == 2; 7 | } 8 | if n == 1 { 9 | return false; 10 | } 11 | let mut k = 3; 12 | while k * k <= n { 13 | if n % k == 0u32 { 14 | return false; 15 | } 16 | k += 2; 17 | } 18 | true 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | 24 | use super::*; 25 | 26 | #[test] 27 | fn test_primetest_0() { 28 | assert!(!primetest(0)) 29 | } 30 | #[test] 31 | fn test_primetest_1() { 32 | assert!(!primetest(1)) 33 | } 34 | #[test] 35 | fn test_primetest_2() { 36 | assert!(primetest(2)) 37 | } 38 | #[test] 39 | fn test_primetest_3() { 40 | assert!(primetest(3)) 41 | } 42 | #[test] 43 | fn test_primetest_4() { 44 | assert!(!primetest(4)) 45 | } 46 | #[test] 47 | fn test_primetest_25() { 48 | assert!(!primetest(25)) 49 | } 50 | #[test] 51 | fn test_primetest_29() { 52 | assert!(primetest(29)) 53 | } 54 | #[test] 55 | fn test_primetest_31() { 56 | assert!(primetest(31)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/simple/src/simple_add.rs: -------------------------------------------------------------------------------- 1 | use mutagen::mutate; 2 | 3 | #[cfg_attr(test, mutate)] 4 | pub fn simple_add() -> i32 { 5 | 1 + 2 6 | } 7 | 8 | #[cfg_attr(test, mutate)] 9 | pub fn simple_add_u8() -> u8 { 10 | 1 + 2 11 | } 12 | 13 | #[cfg_attr(test, mutate)] 14 | pub fn add_repeated_u8() -> u8 { 15 | 1 + 2 + 3 * 2 16 | } 17 | 18 | #[cfg_attr(test, mutate)] 19 | pub fn add_two_u8(x: u8) -> u8 { 20 | x + 2 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | 26 | use super::*; 27 | 28 | #[test] 29 | fn test_simple_add() { 30 | assert_eq!(simple_add(), 3); 31 | } 32 | 33 | #[test] 34 | fn test_simple_add_u8() { 35 | assert_eq!(simple_add_u8(), 3); 36 | } 37 | 38 | #[test] 39 | fn test_add_two_u8() { 40 | assert_eq!(add_two_u8(1), 3); 41 | } 42 | 43 | #[test] 44 | fn test_add_repeated_u8() { 45 | assert_eq!(add_repeated_u8(), 9); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/with-integration-tests-v1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-with-integration-tests-v1" 3 | version = "0.2.0" 4 | authors = ["Samuel Pilz "] 5 | edition = "2018" 6 | 7 | [dev-dependencies] 8 | mutagen = {path = "../../mutagen"} 9 | 10 | [lib] 11 | doctest = false 12 | -------------------------------------------------------------------------------- /examples/with-integration-tests-v1/src/lib.rs: -------------------------------------------------------------------------------- 1 | // calculates fibonacci numbers 2 | #[cfg_attr(test, ::mutagen::mutate)] 3 | pub fn fib(n: u32) -> u32 { 4 | if n == 0 || n == 1 { 5 | return n; 6 | } 7 | fib(n - 1) + fib(n - 2) 8 | } 9 | -------------------------------------------------------------------------------- /examples/with-integration-tests-v1/tests/test_fib.rs: -------------------------------------------------------------------------------- 1 | use example_with_integration_tests_v1::fib; 2 | 3 | #[test] 4 | fn fib_0() { 5 | assert_eq!(0, fib(0)) 6 | } 7 | 8 | #[test] 9 | fn fib_1() { 10 | assert_eq!(1, fib(1)) 11 | } 12 | 13 | #[test] 14 | fn fib_2() { 15 | assert_eq!(1, fib(2)) 16 | } 17 | 18 | #[test] 19 | fn fib_3() { 20 | assert_eq!(2, fib(3)) 21 | } 22 | 23 | #[test] 24 | fn fib_4() { 25 | assert_eq!(3, fib(4)) 26 | } 27 | #[test] 28 | fn fib_5() { 29 | assert_eq!(5, fib(5)) 30 | } 31 | -------------------------------------------------------------------------------- /examples/with-integration-tests-v2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-with-integration-tests-v2" 3 | version = "0.2.0" 4 | authors = ["Samuel Pilz "] 5 | edition = "2018" 6 | 7 | [features] 8 | mutationtest = ["mutagen"] 9 | 10 | [dependencies] 11 | mutagen = {path = "../../mutagen", optional = true} 12 | 13 | [lib] 14 | doctest = false 15 | -------------------------------------------------------------------------------- /examples/with-integration-tests-v2/src/lib.rs: -------------------------------------------------------------------------------- 1 | // calculates fibonacci numbers 2 | #[cfg_attr(feature = "mutationtest", ::mutagen::mutate)] 3 | pub fn fib(n: u32) -> u32 { 4 | if n == 0 || n == 1 { 5 | return n; 6 | } 7 | fib(n - 1) + fib(n - 2) 8 | } 9 | -------------------------------------------------------------------------------- /examples/with-integration-tests-v2/tests/test_fib.rs: -------------------------------------------------------------------------------- 1 | use example_with_integration_tests_v2::fib; 2 | 3 | #[test] 4 | fn fib_0() { 5 | assert_eq!(0, fib(0)) 6 | } 7 | 8 | #[test] 9 | fn fib_1() { 10 | assert_eq!(1, fib(1)) 11 | } 12 | 13 | #[test] 14 | fn fib_2() { 15 | assert_eq!(1, fib(2)) 16 | } 17 | 18 | #[test] 19 | fn fib_3() { 20 | assert_eq!(2, fib(3)) 21 | } 22 | 23 | #[test] 24 | fn fib_4() { 25 | assert_eq!(3, fib(4)) 26 | } 27 | #[test] 28 | fn fib_5() { 29 | assert_eq!(5, fib(5)) 30 | } 31 | -------------------------------------------------------------------------------- /mutagen-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mutagen-core" 3 | version = "0.2.0" 4 | authors = ["Andre Bogus ", "Samuel Pilz "] 5 | edition = "2018" 6 | license = "Apache-2.0/MIT" 7 | 8 | [dependencies] 9 | serde = { version = "1.0.130", features = ["derive"] } 10 | serde_json = "1.0.68" 11 | anyhow = "1.0.57" 12 | json = "0.12.4" 13 | lazy_static = "1.4.0" 14 | 15 | quote = "1.0.9" 16 | proc-macro2 = { version = "1.0.29", features = ["span-locations"] } 17 | syn = { version = "1.0.76", features = ["full", "extra-traits", "fold"] } 18 | 19 | 20 | [features] 21 | # this is a flag that is required for integration tests to allow setting the environment configuration from tests. 22 | # This is not intended to be used for users of `mutagen`. 23 | self_test = [] 24 | 25 | [lib] 26 | doctest = false 27 | -------------------------------------------------------------------------------- /mutagen-core/src/comm.rs: -------------------------------------------------------------------------------- 1 | //! Types and functions for communication between mutagen-processes 2 | //! 3 | //! Mutagen requires several communication-channels to function fully 4 | //! 5 | //! * The procedural macro informs the runner about all baked mutations 6 | //! * The runner informs the test-suite about its mode (mutation or coverage) and additional required information (mutation_id, num_mutations) 7 | //! * The mutators in the test suite inform the runner about coverage-hits 8 | //! 9 | //! Currently, communication from the procedural macro and test-suite is implemented via files in the `target/mutagen` directory. 10 | //! The communication to the test-suite is implemented via environment variables 11 | mod coverage; 12 | mod mutagen_files; 13 | mod mutation; 14 | mod report; 15 | 16 | pub use coverage::{CoverageCollection, CoverageHit}; 17 | pub use mutagen_files::*; 18 | pub use mutation::{BakedMutation, Mutation}; 19 | pub use report::{MutagenReport, MutantStatus}; 20 | -------------------------------------------------------------------------------- /mutagen-core/src/comm/coverage.rs: -------------------------------------------------------------------------------- 1 | use super::BakedMutation; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// A single coverage hit. 5 | #[derive(Debug, Serialize, Deserialize)] 6 | pub struct CoverageHit { 7 | pub mutator_id: usize, 8 | } 9 | 10 | /// A collection that tracks which mutations have been covered. 11 | /// 12 | /// The collection can be created my 13 | #[derive(Debug, Serialize, Deserialize)] 14 | pub struct CoverageCollection { 15 | num_covered: usize, 16 | coverage: Vec, 17 | } 18 | 19 | impl CoverageCollection { 20 | /// Create a collection about coverage where no mutator has been covered. 21 | pub fn new_empty(num_mutations: usize) -> Self { 22 | Self { 23 | num_covered: 0, 24 | coverage: vec![false; num_mutations + 1], 25 | } 26 | } 27 | 28 | /// Create a collection about coverage from a list of coverage hits. 29 | pub fn from_coverage_hits( 30 | num_mutations: usize, 31 | hits: &[CoverageHit], 32 | mutations: &[BakedMutation], 33 | ) -> Self { 34 | let mut coverage = vec![false; num_mutations + 1]; 35 | let mut num_covered = 0; 36 | 37 | for hit in hits { 38 | if !coverage[hit.mutator_id] { 39 | for m in mutations { 40 | if m.mutator_id() == hit.mutator_id { 41 | num_covered += 1; 42 | coverage[m.id()] = true; 43 | } 44 | } 45 | } 46 | } 47 | 48 | Self { 49 | num_covered, 50 | coverage, 51 | } 52 | } 53 | 54 | /// Merge multiple coverage collections into a single one. 55 | pub fn merge<'a>( 56 | num_mutations: usize, 57 | coverages: impl IntoIterator, 58 | ) -> Self { 59 | let mut coverage = vec![false; num_mutations + 1]; 60 | let mut num_covered = 0; 61 | 62 | for c in coverages { 63 | for m_id in 1..=num_mutations { 64 | if c.is_covered(m_id) && !coverage[m_id] { 65 | num_covered += 1; 66 | coverage[m_id] = true; 67 | } 68 | } 69 | } 70 | 71 | Self { 72 | num_covered, 73 | coverage, 74 | } 75 | } 76 | 77 | /// Checks if the given mutation is covered. 78 | pub fn is_covered(&self, m_id: usize) -> bool { 79 | self.coverage[m_id] 80 | } 81 | 82 | /// Returns the number of covered mutations. 83 | pub fn num_covered(&self) -> usize { 84 | self.num_covered 85 | } 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | use super::*; 91 | use crate::comm::Mutation; 92 | 93 | #[test] 94 | fn coverage_collection_empty() { 95 | let c = CoverageCollection::new_empty(2); 96 | 97 | assert!(!c.is_covered(1)); 98 | assert!(!c.is_covered(2)) 99 | } 100 | 101 | #[test] 102 | fn coverage_collection_single_covered() { 103 | let c = CoverageCollection::from_coverage_hits( 104 | 2, 105 | &[CoverageHit { mutator_id: 1 }], 106 | &[Mutation::new_stub().with_id(1, 1)], 107 | ); 108 | 109 | assert!(c.is_covered(1)); 110 | assert!(!c.is_covered(2)); 111 | assert_eq!(c.num_covered(), 1); 112 | } 113 | #[test] 114 | fn coverage_collection_covered_all_mutations_of_mutator() { 115 | let c = CoverageCollection::from_coverage_hits( 116 | 2, 117 | &[CoverageHit { mutator_id: 1 }], 118 | &[ 119 | Mutation::new_stub().with_id(1, 1), 120 | Mutation::new_stub().with_id(2, 1), 121 | ], 122 | ); 123 | 124 | assert!(c.is_covered(1)); 125 | assert!(c.is_covered(2)); 126 | assert_eq!(c.num_covered(), 2); 127 | } 128 | 129 | #[test] 130 | fn coverage_collection_merge() { 131 | let mutations = [ 132 | Mutation::new_stub().with_id(1, 1), 133 | Mutation::new_stub().with_id(2, 2), 134 | Mutation::new_stub().with_id(3, 3), 135 | Mutation::new_stub().with_id(4, 3), 136 | ]; 137 | let c1 = 138 | CoverageCollection::from_coverage_hits(4, &[CoverageHit { mutator_id: 2 }], &mutations); 139 | let c2 = 140 | CoverageCollection::from_coverage_hits(4, &[CoverageHit { mutator_id: 3 }], &mutations); 141 | 142 | let c = CoverageCollection::merge(4, &[c1, c2]); 143 | 144 | assert!(c.is_covered(2)); 145 | assert!(c.is_covered(3)); 146 | assert!(c.is_covered(4)); 147 | assert_eq!(c.num_covered(), 3); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /mutagen-core/src/comm/mutagen_files.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context, Result}; 2 | use serde::{de::DeserializeOwned, Serialize}; 3 | use std::fs::File; 4 | use std::io::{BufRead, BufReader, BufWriter, Write}; 5 | use std::path::{Path, PathBuf}; 6 | use std::process::Command; 7 | use std::str; 8 | 9 | const DEFAULT_MUTAGEN_DIR: &str = "target/mutagen"; 10 | const DEFAULT_MUTAGEN_FILENAME: &str = "mutations"; 11 | const JSON_MUTAGEN_FILENAME: &str = "mutations.json"; 12 | 13 | const DEFAULT_COVERAGE_FILENAME: &str = "coverage"; 14 | 15 | /// Finds the file that contains the descriptions of all mutations as written by the procedural macro 16 | pub fn get_mutations_file() -> Result { 17 | Ok(mutagen_dir()?.join(DEFAULT_MUTAGEN_FILENAME)) 18 | } 19 | 20 | pub fn get_mutations_file_json() -> Result { 21 | Ok(mutagen_dir()?.join(JSON_MUTAGEN_FILENAME)) 22 | } 23 | 24 | pub fn get_coverage_file() -> Result { 25 | Ok(mutagen_dir()?.join(DEFAULT_COVERAGE_FILENAME)) 26 | } 27 | 28 | /// queries `cargo` for the workspace root and locates the directory to write mutagen-specific information 29 | fn mutagen_dir() -> Result { 30 | let metadata = Command::new("cargo").arg("metadata").output()?; 31 | if !metadata.status.success() { 32 | bail!("{}", str::from_utf8(&metadata.stderr)?); 33 | } 34 | let meta_json = json::parse(str::from_utf8(&metadata.stdout)?)?; 35 | let root_dir = Path::new( 36 | meta_json["workspace_root"] 37 | .as_str() 38 | .with_context(|| "cargo metadata misses workspace_root")?, 39 | ); 40 | Ok(root_dir.join(DEFAULT_MUTAGEN_DIR)) 41 | } 42 | 43 | pub fn read_items(filepath: &Path) -> Result> { 44 | BufReader::new(File::open(filepath)?) 45 | .lines() 46 | .map(|line| serde_json::from_str(&line?).with_context(|| "mutation format error")) 47 | .collect() 48 | } 49 | 50 | pub fn append_item(file: &mut File, item: &T) -> Result<()> { 51 | let mut w = BufWriter::new(file); 52 | serde_json::to_writer(&mut w, item)?; 53 | writeln!(&mut w)?; // write newline 54 | Ok(()) 55 | } 56 | -------------------------------------------------------------------------------- /mutagen-core/src/comm/mutation.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use proc_macro2::Span; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::transformer::TransformContext; 8 | 9 | /// description of a single mutation baked into the code with a given id 10 | #[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] 11 | pub struct BakedMutation { 12 | id: usize, 13 | // id of the mutator that generates this mutation 14 | mutator_id: usize, 15 | mutation: Mutation, 16 | } 17 | 18 | // TODO: document fields and getters 19 | /// Mutation in source code 20 | #[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] 21 | pub struct Mutation { 22 | pub impl_name: Option, 23 | pub fn_name: Option, 24 | pub mutator: String, 25 | pub original_code: String, 26 | pub mutated_code: String, 27 | pub source_file: PathBuf, 28 | pub location_in_file: String, 29 | } 30 | 31 | impl Mutation { 32 | pub fn new_spanned( 33 | context: &TransformContext, 34 | mutator: String, 35 | original_code: String, 36 | mutated_code: String, 37 | span: Span, 38 | ) -> Self { 39 | let impl_name = context.impl_name.clone(); 40 | let fn_name = context.fn_name.clone(); 41 | let start = span.start(); 42 | let end = span.end(); 43 | let source_file = span.unwrap().source_file().path(); 44 | let location_in_file = format!( 45 | "{}:{}-{}:{}", 46 | start.line, start.column, end.line, end.column 47 | ); 48 | 49 | Self { 50 | impl_name, 51 | fn_name, 52 | mutator, 53 | original_code, 54 | mutated_code, 55 | source_file, 56 | location_in_file, 57 | } 58 | } 59 | 60 | /// Give the mutation an id and mutator id. 61 | /// 62 | /// This creates a baked mutation. 63 | pub fn with_id(self, id: usize, mutator_id: usize) -> BakedMutation { 64 | BakedMutation { 65 | id, 66 | mutator_id, 67 | mutation: self, 68 | } 69 | } 70 | 71 | /// construct a string representation of the mutation 72 | pub fn mutation_description(&self) -> String { 73 | if self.mutated_code.is_empty() { 74 | format!("remove `{}`", &self.original_code) 75 | } else if self.original_code.is_empty() { 76 | format!("insert `{}`", &self.mutated_code) 77 | } else { 78 | format!( 79 | "replace `{}` with `{}`", 80 | &self.original_code, &self.mutated_code, 81 | ) 82 | } 83 | } 84 | 85 | pub fn context_description_in_brackets(&self) -> String { 86 | match (&self.fn_name, &self.impl_name) { 87 | (None, None) => format!(""), 88 | (Some(fn_name), None) => format!("(fn {})", fn_name), 89 | (None, Some(impl_name)) => format!("(impl {})", impl_name), 90 | (Some(fn_name), Some(impl_name)) => format!("(fn {}::{})", impl_name, fn_name), 91 | } 92 | } 93 | } 94 | 95 | impl BakedMutation { 96 | pub fn id(&self) -> usize { 97 | self.id 98 | } 99 | 100 | pub fn mutator_id(&self) -> usize { 101 | self.mutator_id 102 | } 103 | 104 | pub fn mutator_name(&self) -> &str { 105 | self.mutation.mutator.deref() 106 | } 107 | 108 | pub fn fn_name(&self) -> Option<&str> { 109 | // TODO: use Option::deref instead 110 | self.mutation.fn_name.as_deref() 111 | } 112 | 113 | pub fn original_code(&self) -> &str { 114 | self.mutation.original_code.deref() 115 | } 116 | 117 | pub fn mutated_code(&self) -> &str { 118 | self.mutation.mutated_code.deref() 119 | } 120 | 121 | pub fn source_file(&self) -> &Path { 122 | self.mutation.source_file.deref() 123 | } 124 | pub fn location_in_file(&self) -> &str { 125 | self.mutation.location_in_file.deref() 126 | } 127 | pub fn mutation_description(&self) -> String { 128 | self.mutation.mutation_description() 129 | } 130 | pub fn context_description_in_brackets(&self) -> String { 131 | self.mutation.context_description_in_brackets() 132 | } 133 | } 134 | 135 | impl AsRef for BakedMutation { 136 | fn as_ref(&self) -> &Mutation { 137 | &self.mutation 138 | } 139 | } 140 | 141 | #[cfg(test)] 142 | impl Mutation { 143 | /// Create a new mutation for testing purposes. 144 | pub fn new_stub() -> Self { 145 | Self { 146 | impl_name: None, 147 | fn_name: None, 148 | mutator: "stub".to_owned(), 149 | original_code: "stub".to_owned(), 150 | mutated_code: "stub".to_owned(), 151 | source_file: PathBuf::new(), 152 | location_in_file: "stub".to_owned(), 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /mutagen-core/src/comm/report.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::{BTreeMap, HashMap}; 3 | use std::fmt; 4 | 5 | use super::BakedMutation; 6 | 7 | #[derive(Serialize, Deserialize, Default)] 8 | pub struct MutagenReport { 9 | mutant_results: HashMap, 10 | summary: ReportSummary, 11 | } 12 | 13 | #[derive(Copy, Clone, Default, Serialize, Deserialize)] 14 | pub struct ReportSummary { 15 | num_mutations: usize, 16 | killed: usize, 17 | timeout: usize, 18 | survived: usize, 19 | not_covered: usize, 20 | } 21 | 22 | impl MutagenReport { 23 | pub fn new() -> Self { 24 | Self::default() 25 | } 26 | 27 | pub fn add_mutation_result(&mut self, mutation: BakedMutation, status: MutantStatus) { 28 | assert!(!self.mutant_results.contains_key(&mutation)); 29 | // TODO: instead use: .expect_none("mutation already added"); 30 | self.mutant_results.insert(mutation, status); 31 | self.summary.add_mutation_result(status); 32 | } 33 | 34 | /// creates a map of mutations per file. 35 | /// 36 | /// The map gets iterated in alphabetical order of the files and the list of mutations is sorted by mutation-id 37 | fn mutations_per_file( 38 | &self, 39 | ) -> BTreeMap<&std::path::Path, Vec<(&BakedMutation, MutantStatus)>> { 40 | let mut map = BTreeMap::new(); 41 | // collect mutations by source file 42 | for (m, s) in &self.mutant_results { 43 | map.entry(m.source_file()) 44 | .or_insert_with(Vec::new) 45 | .push((m, *s)); 46 | } 47 | // sort list of mutations per source file by id 48 | for ms in map.values_mut() { 49 | ms.sort_unstable_by_key(|(m, _)| m.id()); 50 | } 51 | map 52 | } 53 | 54 | pub fn print_survived(&self) { 55 | println!("SURVIVED"); 56 | let mutations_per_file = self.mutations_per_file().into_iter().collect::>(); 57 | 58 | for (file, mutations) in mutations_per_file { 59 | let num_mutations = mutations.len(); 60 | // TODO: use mutations.drain_filter 61 | let survived = mutations 62 | .into_iter() 63 | .filter(|(_, s)| s.survived()) 64 | .collect::>(); 65 | let num_survived = survived.len(); 66 | 67 | println!(" {}", file.display()); 68 | if num_survived == 0 { 69 | println!(" all {} mutants killed", num_mutations); 70 | } else if num_survived == num_mutations { 71 | println!(" all {} mutants survived", num_survived); 72 | } else { 73 | println!( 74 | " {}/{}({:.2}%) mutants survived", 75 | num_survived, 76 | num_mutations, 77 | compute_percent(num_mutations, num_survived) 78 | ); 79 | } 80 | for (m, s) in survived { 81 | println!( 82 | " {}: {} at {}{}{}", 83 | m.id(), 84 | m.mutation_description(), 85 | m.location_in_file(), 86 | m.context_description_in_brackets(), 87 | if s == MutantStatus::NotCovered { 88 | format!(" {}", MutantStatus::NotCovered) 89 | } else { 90 | "".to_owned() 91 | }, 92 | ); 93 | } 94 | } 95 | } 96 | 97 | pub fn summary(&self) -> ReportSummary { 98 | self.summary 99 | } 100 | } 101 | 102 | impl ReportSummary { 103 | fn add_mutation_result(&mut self, status: MutantStatus) { 104 | self.num_mutations += 1; 105 | match status { 106 | MutantStatus::NotCovered => { 107 | self.not_covered += 1; 108 | self.survived += 1; 109 | } 110 | MutantStatus::Survived => self.survived += 1, 111 | MutantStatus::Killed(_) => self.killed += 1, 112 | MutantStatus::Timeout => { 113 | self.timeout += 1; 114 | self.killed += 1; 115 | } 116 | } 117 | } 118 | 119 | pub fn print(&self) { 120 | let percent_mutations_killed = compute_percent(self.num_mutations, self.killed); 121 | let percent_mutations_timeout = compute_percent(self.num_mutations, self.timeout); 122 | let percent_mutations_survived = compute_percent(self.num_mutations, self.survived); 123 | let percent_mutations_not_covered = compute_percent(self.num_mutations, self.not_covered); 124 | 125 | println!(); 126 | println!("{} generated mutants", self.num_mutations); 127 | println!( 128 | "{}({:.2}%) mutants killed, {}({:.2}%) by timeout", 129 | self.killed, percent_mutations_killed, self.timeout, percent_mutations_timeout, 130 | ); 131 | println!( 132 | "{}({:.2}%) mutants SURVIVED, {}({:.2}%) NOT COVERED", 133 | self.survived, 134 | percent_mutations_survived, 135 | self.not_covered, 136 | percent_mutations_not_covered, 137 | ); 138 | } 139 | } 140 | 141 | /// Result from a test run 142 | #[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] 143 | pub enum MutantStatus { 144 | /// The test suite did not cover the mutator 145 | NotCovered, 146 | /// test pass 147 | Survived, 148 | /// the test broke with an error code 149 | Killed(Option), 150 | /// the test timed out 151 | Timeout, 152 | } 153 | 154 | impl MutantStatus { 155 | fn survived(self) -> bool { 156 | self == Self::NotCovered || self == Self::Survived 157 | } 158 | } 159 | 160 | impl fmt::Display for MutantStatus { 161 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 162 | match self { 163 | Self::NotCovered => write!(f, "NOT COVERED"), 164 | Self::Survived => write!(f, "SURVIVED"), 165 | Self::Killed(_) => write!(f, "killed"), 166 | Self::Timeout => write!(f, "killed (timeout)"), 167 | } 168 | } 169 | } 170 | 171 | fn compute_percent(total: usize, num: usize) -> f64 { 172 | 100.0 * num as f64 / total as f64 173 | } 174 | -------------------------------------------------------------------------------- /mutagen-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | // proc-macro-span feature is required because `proc_macro2` does not export the api for getting source files for spans 2 | #![feature(proc_macro_span)] 3 | #![feature(specialization)] 4 | 5 | mod runtime_config; 6 | mod transformer; 7 | 8 | pub mod comm; 9 | pub mod mutator; 10 | 11 | pub use runtime_config::MutagenRuntimeConfig; 12 | 13 | pub use transformer::do_transform_item; 14 | -------------------------------------------------------------------------------- /mutagen-core/src/mutator.rs: -------------------------------------------------------------------------------- 1 | // the modules below are public to enable the use of types in that modules at runtime 2 | pub mod mutator_binop_bit; 3 | pub mod mutator_binop_bool; 4 | pub mod mutator_binop_cmp; 5 | pub mod mutator_binop_eq; 6 | pub mod mutator_binop_num; 7 | pub mod mutator_lit_bool; 8 | pub mod mutator_lit_int; 9 | pub mod mutator_lit_str; 10 | pub mod mutator_stmt_call; 11 | pub mod mutator_unop_not; 12 | -------------------------------------------------------------------------------- /mutagen-core/src/mutator/mutator_binop_bit.rs: -------------------------------------------------------------------------------- 1 | //! Mutator for binary bit-operations `|`, `&`, `^`. 2 | 3 | use std::convert::TryFrom; 4 | use std::ops::Deref; 5 | use std::ops::{BitAnd, BitOr, BitXor}; 6 | 7 | use proc_macro2::Span; 8 | use quote::quote_spanned; 9 | use syn::spanned::Spanned; 10 | use syn::{BinOp, Expr}; 11 | 12 | use crate::comm::Mutation; 13 | use crate::transformer::transform_info::SharedTransformInfo; 14 | use crate::transformer::TransformContext; 15 | 16 | use crate::MutagenRuntimeConfig; 17 | 18 | pub fn run_and, R>( 19 | mutator_id: usize, 20 | left: L, 21 | right: R, 22 | runtime: impl Deref, 23 | ) -> >::Output { 24 | runtime.covered(mutator_id); 25 | let mutations = MutationBinopBit::possible_mutations(BinopBit::And); 26 | if let Some(m) = runtime.get_mutation_for_mutator(mutator_id, &mutations) { 27 | match m.op { 28 | BinopBit::Or => left.and_may_or(right), 29 | BinopBit::Xor => left.and_may_xor(right), 30 | _ => unreachable!(), 31 | } 32 | } else { 33 | left & right 34 | } 35 | } 36 | pub fn run_or, R>( 37 | mutator_id: usize, 38 | left: L, 39 | right: R, 40 | runtime: impl Deref, 41 | ) -> >::Output { 42 | runtime.covered(mutator_id); 43 | let mutations = MutationBinopBit::possible_mutations(BinopBit::Or); 44 | if let Some(m) = runtime.get_mutation_for_mutator(mutator_id, &mutations) { 45 | match m.op { 46 | BinopBit::And => left.or_may_and(right), 47 | BinopBit::Xor => left.or_may_xor(right), 48 | _ => unreachable!(), 49 | } 50 | } else { 51 | left | right 52 | } 53 | } 54 | pub fn run_xor, R>( 55 | mutator_id: usize, 56 | left: L, 57 | right: R, 58 | runtime: impl Deref, 59 | ) -> >::Output { 60 | runtime.covered(mutator_id); 61 | let mutations = MutationBinopBit::possible_mutations(BinopBit::Xor); 62 | if let Some(m) = runtime.get_mutation_for_mutator(mutator_id, &mutations) { 63 | match m.op { 64 | BinopBit::And => left.xor_may_and(right), 65 | BinopBit::Or => left.xor_may_or(right), 66 | _ => unreachable!(), 67 | } 68 | } else { 69 | left ^ right 70 | } 71 | } 72 | 73 | pub fn transform( 74 | e: Expr, 75 | transform_info: &SharedTransformInfo, 76 | context: &TransformContext, 77 | ) -> Expr { 78 | let e = match ExprBinopBit::try_from(e) { 79 | Ok(e) => e, 80 | Err(e) => return e, 81 | }; 82 | 83 | let mutator_id = transform_info.add_mutations( 84 | MutationBinopBit::possible_mutations(e.op) 85 | .iter() 86 | .map(|m| m.to_mutation(&e, context)), 87 | ); 88 | 89 | let left = &e.left; 90 | let right = &e.right; 91 | 92 | let run_fn = match e.op { 93 | BinopBit::And => quote_spanned! {e.span()=> run_and}, 94 | BinopBit::Or => quote_spanned! {e.span()=> run_or}, 95 | BinopBit::Xor => quote_spanned! {e.span()=> run_xor}, 96 | }; 97 | let op_token = e.op_token; 98 | let tmp_var = transform_info.get_next_tmp_var(op_token.span()); 99 | syn::parse2(quote_spanned! {e.span()=> 100 | { 101 | let #tmp_var = #left; 102 | if false {#tmp_var #op_token #right} else { 103 | ::mutagen::mutator::mutator_binop_bit::#run_fn( 104 | #mutator_id, 105 | #tmp_var, 106 | #right, 107 | ::mutagen::MutagenRuntimeConfig::get_default() 108 | ) 109 | } 110 | } 111 | }) 112 | .expect("transformed code invalid") 113 | } 114 | 115 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 116 | struct MutationBinopBit { 117 | op: BinopBit, 118 | } 119 | 120 | impl MutationBinopBit { 121 | fn possible_mutations(original_op: BinopBit) -> Vec { 122 | [BinopBit::And, BinopBit::Or, BinopBit::Xor] 123 | .iter() 124 | .copied() 125 | .filter(|&op| op != original_op) 126 | .map(|op| MutationBinopBit { op }) 127 | .collect() 128 | } 129 | 130 | fn to_mutation(self, original_expr: &ExprBinopBit, context: &TransformContext) -> Mutation { 131 | Mutation::new_spanned( 132 | context, 133 | "binop_bit".to_owned(), 134 | format!("{}", original_expr.op), 135 | format!("{}", self.op), 136 | original_expr.span(), 137 | ) 138 | } 139 | } 140 | 141 | #[derive(Clone, Debug)] 142 | struct ExprBinopBit { 143 | op: BinopBit, 144 | left: Expr, 145 | right: Expr, 146 | op_token: syn::BinOp, 147 | } 148 | 149 | impl TryFrom for ExprBinopBit { 150 | type Error = Expr; 151 | fn try_from(expr: Expr) -> Result { 152 | match expr { 153 | Expr::Binary(expr) => match expr.op { 154 | BinOp::BitAnd(_) => Ok(ExprBinopBit { 155 | op: BinopBit::And, 156 | left: *expr.left, 157 | right: *expr.right, 158 | op_token: expr.op, 159 | }), 160 | BinOp::BitOr(_) => Ok(ExprBinopBit { 161 | op: BinopBit::Or, 162 | left: *expr.left, 163 | right: *expr.right, 164 | op_token: expr.op, 165 | }), 166 | BinOp::BitXor(_) => Ok(ExprBinopBit { 167 | op: BinopBit::Xor, 168 | left: *expr.left, 169 | right: *expr.right, 170 | op_token: expr.op, 171 | }), 172 | _ => Err(Expr::Binary(expr)), 173 | }, 174 | _ => Err(expr), 175 | } 176 | } 177 | } 178 | 179 | impl syn::spanned::Spanned for ExprBinopBit { 180 | fn span(&self) -> Span { 181 | self.op_token.span() 182 | } 183 | } 184 | 185 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] 186 | enum BinopBit { 187 | And, 188 | Or, 189 | Xor, 190 | } 191 | 192 | use std::fmt; 193 | 194 | impl fmt::Display for BinopBit { 195 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 196 | match self { 197 | BinopBit::And => write!(f, "&"), 198 | BinopBit::Or => write!(f, "|"), 199 | BinopBit::Xor => write!(f, "^"), 200 | } 201 | } 202 | } 203 | 204 | // specification of the traits `AndToOr`, `OrToAnd`, ... 205 | // 206 | // These traits consist of a function `max_x` that panics if the operation `x` 207 | // cannot be performed due to type constraints 208 | macro_rules! binary_x_to_y { 209 | { $($may_ty:ident, $may_fn:ident, $t1:ident, $t2:ident, $t2_op:tt,)* } => { 210 | $( 211 | trait $may_ty { 212 | type Output; 213 | fn $may_fn(self, r: R) -> Self::Output; 214 | } 215 | 216 | impl $may_ty for L where L: $t1 { 217 | type Output = >::Output; 218 | default fn $may_fn(self, _r: R) -> >::Output { 219 | MutagenRuntimeConfig::get_default().optimistic_assumption_failed(); 220 | } 221 | } 222 | 223 | impl $may_ty for L 224 | where 225 | L: $t1, 226 | L: $t2, 227 | >::Output: Into<>::Output>, 228 | { 229 | fn $may_fn(self, r: R) -> Self::Output { 230 | (self $t2_op r).into() 231 | } 232 | } 233 | )* 234 | 235 | } 236 | } 237 | 238 | binary_x_to_y!( 239 | AndToOr, and_may_or, BitAnd, BitOr, |, 240 | AndToXor, and_may_xor, BitAnd, BitXor, ^, 241 | OrToAnd, or_may_and, BitOr, BitAnd, &, 242 | OrToXor, or_may_xor, BitOr, BitXor, ^, 243 | XorToAnd, xor_may_and, BitXor, BitAnd, &, 244 | XorToOr, xor_may_or, BitXor, BitOr, |, 245 | ); 246 | 247 | #[cfg(test)] 248 | mod tests { 249 | 250 | use super::*; 251 | 252 | #[test] 253 | fn and_inactive() { 254 | let result = run_and(1, 0b11, 0b10, &MutagenRuntimeConfig::without_mutation()); 255 | assert_eq!(result, 0b10); 256 | } 257 | #[test] 258 | fn and_active1() { 259 | let result = run_and(1, 0b11, 0b10, &MutagenRuntimeConfig::with_mutation_id(1)); 260 | assert_eq!(result, 0b11); 261 | } 262 | #[test] 263 | fn and_active2() { 264 | let result = run_and(1, 0b11, 0b10, &MutagenRuntimeConfig::with_mutation_id(2)); 265 | assert_eq!(result, 0b01); 266 | } 267 | 268 | #[test] 269 | fn or_inactive() { 270 | let result = run_or(1, 0b11, 0b10, &MutagenRuntimeConfig::without_mutation()); 271 | assert_eq!(result, 0b11); 272 | } 273 | #[test] 274 | fn or_active1() { 275 | let result = run_or(1, 0b11, 0b10, &MutagenRuntimeConfig::with_mutation_id(1)); 276 | assert_eq!(result, 0b10); 277 | } 278 | #[test] 279 | fn or_active2() { 280 | let result = run_or(1, 0b11, 0b10, &MutagenRuntimeConfig::with_mutation_id(2)); 281 | assert_eq!(result, 0b01); 282 | } 283 | 284 | #[test] 285 | fn xor_inactive() { 286 | let result = run_xor(1, 0b11, 0b10, &MutagenRuntimeConfig::without_mutation()); 287 | assert_eq!(result, 0b01); 288 | } 289 | #[test] 290 | fn xor_active1() { 291 | let result = run_xor(1, 0b11, 0b10, &MutagenRuntimeConfig::with_mutation_id(1)); 292 | assert_eq!(result, 0b10); 293 | } 294 | #[test] 295 | fn xor_active2() { 296 | let result = run_xor(1, 0b11, 0b10, &MutagenRuntimeConfig::with_mutation_id(2)); 297 | assert_eq!(result, 0b11); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /mutagen-core/src/mutator/mutator_binop_bool.rs: -------------------------------------------------------------------------------- 1 | //! Mutator for binary operations `&&` and `||`. 2 | 3 | use std::convert::TryFrom; 4 | use std::ops::Deref; 5 | 6 | use proc_macro2::{Span, TokenStream}; 7 | use quote::quote_spanned; 8 | use syn::spanned::Spanned; 9 | use syn::{BinOp, Expr}; 10 | 11 | use crate::comm::Mutation; 12 | use crate::transformer::transform_info::SharedTransformInfo; 13 | use crate::transformer::TransformContext; 14 | 15 | use crate::MutagenRuntimeConfig; 16 | 17 | pub fn run_left( 18 | mutator_id: usize, 19 | original_op: BinopBool, 20 | left: bool, 21 | runtime: impl Deref, 22 | ) -> Option { 23 | runtime.covered(mutator_id); 24 | let mutations = MutationBinopBool::possible_mutations(original_op); 25 | let op = runtime 26 | .get_mutation_for_mutator(mutator_id, &mutations) 27 | .map(|m| m.op) 28 | .unwrap_or(original_op); 29 | op.short_circuit_left(left) 30 | } 31 | 32 | pub fn transform( 33 | e: Expr, 34 | transform_info: &SharedTransformInfo, 35 | context: &TransformContext, 36 | ) -> Expr { 37 | let e = match ExprBinopBool::try_from(e) { 38 | Ok(e) => e, 39 | Err(e) => return e, 40 | }; 41 | 42 | let mutator_id = transform_info.add_mutations( 43 | MutationBinopBool::possible_mutations(e.op) 44 | .iter() 45 | .map(|m| m.to_mutation(&e, context)), 46 | ); 47 | 48 | let left = &e.left; 49 | let right = &e.right; 50 | let op = e.op_tokens(); 51 | 52 | syn::parse2(quote_spanned! {e.span=> 53 | if let Some(x) = ::mutagen::mutator::mutator_binop_bool::run_left( 54 | #mutator_id, 55 | #op, 56 | #left, 57 | ::mutagen::MutagenRuntimeConfig::get_default() 58 | ) { 59 | x 60 | } else { 61 | #right 62 | } 63 | }) 64 | .expect("transformed code invalid") 65 | } 66 | 67 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 68 | struct MutationBinopBool { 69 | op: BinopBool, 70 | } 71 | 72 | impl MutationBinopBool { 73 | fn possible_mutations(original_op: BinopBool) -> Vec { 74 | [BinopBool::And, BinopBool::Or] 75 | .iter() 76 | .copied() 77 | .filter(|&op| op != original_op) 78 | .map(|op| MutationBinopBool { op }) 79 | .collect() 80 | } 81 | 82 | fn to_mutation(self, original_op: &ExprBinopBool, context: &TransformContext) -> Mutation { 83 | Mutation::new_spanned( 84 | context, 85 | "binop_bool".to_owned(), 86 | format!("{}", original_op), 87 | format!("{}", self.op), 88 | original_op.span, 89 | ) 90 | } 91 | } 92 | 93 | #[derive(Clone, Debug)] 94 | struct ExprBinopBool { 95 | op: BinopBool, 96 | left: Expr, 97 | right: Expr, 98 | span: Span, 99 | } 100 | 101 | impl TryFrom for ExprBinopBool { 102 | type Error = Expr; 103 | fn try_from(expr: Expr) -> Result { 104 | match expr { 105 | Expr::Binary(expr) => match expr.op { 106 | BinOp::And(t) => Ok(ExprBinopBool { 107 | op: BinopBool::And, 108 | left: *expr.left, 109 | right: *expr.right, 110 | span: t.span(), 111 | }), 112 | BinOp::Or(t) => Ok(ExprBinopBool { 113 | op: BinopBool::Or, 114 | left: *expr.left, 115 | right: *expr.right, 116 | span: t.span(), 117 | }), 118 | _ => Err(Expr::Binary(expr)), 119 | }, 120 | _ => Err(expr), 121 | } 122 | } 123 | } 124 | 125 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] 126 | pub enum BinopBool { 127 | And, 128 | Or, 129 | } 130 | 131 | impl BinopBool { 132 | pub fn short_circuit_left(self, left: bool) -> Option { 133 | match self { 134 | BinopBool::And if !left => Some(false), 135 | BinopBool::Or if left => Some(true), 136 | _ => None, 137 | } 138 | } 139 | } 140 | 141 | impl ExprBinopBool { 142 | fn op_tokens(&self) -> TokenStream { 143 | let mut tokens = TokenStream::new(); 144 | tokens.extend(quote_spanned!(self.span=> 145 | ::mutagen::mutator::mutator_binop_bool::BinopBool::)); 146 | tokens.extend(match self.op { 147 | BinopBool::And => quote_spanned!(self.span=> And), 148 | BinopBool::Or => quote_spanned!(self.span=> Or), 149 | }); 150 | tokens 151 | } 152 | } 153 | 154 | use std::fmt; 155 | 156 | impl fmt::Display for BinopBool { 157 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 158 | match self { 159 | BinopBool::And => write!(f, "&&"), 160 | BinopBool::Or => write!(f, "||"), 161 | } 162 | } 163 | } 164 | 165 | impl fmt::Display for ExprBinopBool { 166 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 167 | write!(f, "{}", self.op) 168 | } 169 | } 170 | 171 | #[cfg(test)] 172 | mod tests { 173 | 174 | use super::*; 175 | 176 | #[test] 177 | fn possible_mutations_and() { 178 | assert_eq!( 179 | MutationBinopBool::possible_mutations(BinopBool::And), 180 | vec![MutationBinopBool { op: BinopBool::Or }] 181 | ) 182 | } 183 | 184 | #[test] 185 | fn possible_mutations_or() { 186 | assert_eq!( 187 | MutationBinopBool::possible_mutations(BinopBool::Or), 188 | vec![MutationBinopBool { op: BinopBool::And }] 189 | ) 190 | } 191 | 192 | #[test] 193 | fn short_circuit_left_and_false() { 194 | assert_eq!(BinopBool::And.short_circuit_left(false), Some(false)) 195 | } 196 | #[test] 197 | fn short_circuit_left_and_true() { 198 | assert_eq!(BinopBool::And.short_circuit_left(true), None) 199 | } 200 | 201 | #[test] 202 | fn short_circuit_left_or_false() { 203 | assert_eq!(BinopBool::Or.short_circuit_left(false), None) 204 | } 205 | #[test] 206 | fn short_circuit_left_or_true() { 207 | assert_eq!(BinopBool::Or.short_circuit_left(true), Some(true)) 208 | } 209 | 210 | #[test] 211 | fn mutator_and_inactive() { 212 | assert_eq!( 213 | run_left( 214 | 1, 215 | BinopBool::And, 216 | true, 217 | &MutagenRuntimeConfig::without_mutation() 218 | ), 219 | None 220 | ); 221 | assert_eq!( 222 | run_left( 223 | 1, 224 | BinopBool::And, 225 | false, 226 | &MutagenRuntimeConfig::without_mutation() 227 | ), 228 | Some(false) 229 | ); 230 | } 231 | #[test] 232 | fn mutator_and_active() { 233 | assert_eq!( 234 | run_left( 235 | 1, 236 | BinopBool::And, 237 | true, 238 | &MutagenRuntimeConfig::with_mutation_id(1) 239 | ), 240 | Some(true) 241 | ); 242 | assert_eq!( 243 | run_left( 244 | 1, 245 | BinopBool::And, 246 | false, 247 | &MutagenRuntimeConfig::with_mutation_id(1) 248 | ), 249 | None 250 | ); 251 | } 252 | 253 | #[test] 254 | fn mutator_or_inactive() { 255 | assert_eq!( 256 | run_left( 257 | 1, 258 | BinopBool::Or, 259 | true, 260 | &MutagenRuntimeConfig::without_mutation() 261 | ), 262 | Some(true) 263 | ); 264 | assert_eq!( 265 | run_left( 266 | 1, 267 | BinopBool::Or, 268 | false, 269 | &MutagenRuntimeConfig::without_mutation() 270 | ), 271 | None 272 | ); 273 | } 274 | #[test] 275 | fn mutator_or_active() { 276 | assert_eq!( 277 | run_left( 278 | 1, 279 | BinopBool::Or, 280 | true, 281 | &MutagenRuntimeConfig::with_mutation_id(1) 282 | ), 283 | None 284 | ); 285 | assert_eq!( 286 | run_left( 287 | 1, 288 | BinopBool::Or, 289 | false, 290 | &MutagenRuntimeConfig::with_mutation_id(1) 291 | ), 292 | Some(false) 293 | ); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /mutagen-core/src/mutator/mutator_binop_cmp.rs: -------------------------------------------------------------------------------- 1 | //! Mutator for comparison operations `<`, `<=`, `=>`, `>` 2 | 3 | use std::convert::TryFrom; 4 | use std::ops::Deref; 5 | 6 | use proc_macro2::{Span, TokenStream}; 7 | use quote::quote_spanned; 8 | use syn::spanned::Spanned; 9 | use syn::{BinOp, Expr}; 10 | 11 | use crate::comm::Mutation; 12 | use crate::transformer::transform_info::SharedTransformInfo; 13 | use crate::transformer::TransformContext; 14 | 15 | use crate::MutagenRuntimeConfig; 16 | 17 | pub fn run, R>( 18 | mutator_id: usize, 19 | left: L, 20 | right: R, 21 | original_op: BinopCmp, 22 | runtime: impl Deref, 23 | ) -> bool { 24 | runtime.covered(mutator_id); 25 | let mutations = MutationBinopCmp::possible_mutations(original_op); 26 | if let Some(m) = runtime.get_mutation_for_mutator(mutator_id, &mutations) { 27 | m.mutate(left, right) 28 | } else { 29 | original_op.cmp(left, right) 30 | } 31 | } 32 | 33 | pub fn transform( 34 | e: Expr, 35 | transform_info: &SharedTransformInfo, 36 | context: &TransformContext, 37 | ) -> Expr { 38 | let e = match ExprBinopCmp::try_from(e) { 39 | Ok(e) => e, 40 | Err(e) => return e, 41 | }; 42 | 43 | let mutator_id = transform_info.add_mutations( 44 | MutationBinopCmp::possible_mutations(e.op) 45 | .iter() 46 | .map(|m| m.to_mutation(&e, context)), 47 | ); 48 | 49 | let left = &e.left; 50 | let right = &e.right; 51 | let op = e.op_tokens(); 52 | 53 | syn::parse2(quote_spanned! {e.span=> 54 | ::mutagen::mutator::mutator_binop_cmp::run( 55 | #mutator_id, 56 | &(#left), 57 | &(#right), 58 | #op, 59 | ::mutagen::MutagenRuntimeConfig::get_default() 60 | ) 61 | }) 62 | .expect("transformed code invalid") 63 | } 64 | 65 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 66 | struct MutationBinopCmp { 67 | op: BinopCmp, 68 | } 69 | 70 | impl MutationBinopCmp { 71 | fn possible_mutations(original_op: BinopCmp) -> Vec { 72 | [BinopCmp::Lt, BinopCmp::Le, BinopCmp::Ge, BinopCmp::Gt] 73 | .iter() 74 | .copied() 75 | .filter(|&op| op != original_op) 76 | .map(|op| MutationBinopCmp { op }) 77 | .collect() 78 | } 79 | 80 | fn mutate, R>(self, left: L, right: R) -> bool { 81 | self.op.cmp(left, right) 82 | } 83 | 84 | fn to_mutation(self, original_op: &ExprBinopCmp, context: &TransformContext) -> Mutation { 85 | Mutation::new_spanned( 86 | context, 87 | "binop_cmp".to_owned(), 88 | format!("{}", original_op.op), 89 | format!("{}", self.op), 90 | original_op.span, 91 | ) 92 | } 93 | } 94 | 95 | #[derive(Clone, Debug)] 96 | struct ExprBinopCmp { 97 | op: BinopCmp, 98 | left: Expr, 99 | right: Expr, 100 | span: Span, 101 | } 102 | 103 | impl TryFrom for ExprBinopCmp { 104 | type Error = Expr; 105 | fn try_from(expr: Expr) -> Result { 106 | match expr { 107 | Expr::Binary(expr) => match expr.op { 108 | BinOp::Lt(t) => Ok(ExprBinopCmp { 109 | op: BinopCmp::Lt, 110 | left: *expr.left, 111 | right: *expr.right, 112 | span: t.span(), 113 | }), 114 | BinOp::Le(t) => Ok(ExprBinopCmp { 115 | op: BinopCmp::Le, 116 | left: *expr.left, 117 | right: *expr.right, 118 | span: t.span(), 119 | }), 120 | BinOp::Ge(t) => Ok(ExprBinopCmp { 121 | op: BinopCmp::Ge, 122 | left: *expr.left, 123 | right: *expr.right, 124 | span: t.span(), 125 | }), 126 | BinOp::Gt(t) => Ok(ExprBinopCmp { 127 | op: BinopCmp::Gt, 128 | left: *expr.left, 129 | right: *expr.right, 130 | span: t.span(), 131 | }), 132 | _ => Err(Expr::Binary(expr)), 133 | }, 134 | _ => Err(expr), 135 | } 136 | } 137 | } 138 | 139 | impl ExprBinopCmp { 140 | fn op_tokens(&self) -> TokenStream { 141 | let mut tokens = TokenStream::new(); 142 | tokens.extend(quote_spanned!(self.span=> 143 | ::mutagen::mutator::mutator_binop_cmp::BinopCmp::)); 144 | tokens.extend(match self.op { 145 | BinopCmp::Lt => quote_spanned!(self.span=> Lt), 146 | BinopCmp::Le => quote_spanned!(self.span=> Le), 147 | BinopCmp::Ge => quote_spanned!(self.span=> Ge), 148 | BinopCmp::Gt => quote_spanned!(self.span=> Gt), 149 | }); 150 | tokens 151 | } 152 | } 153 | 154 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] 155 | pub enum BinopCmp { 156 | Lt, 157 | Le, 158 | Ge, 159 | Gt, 160 | } 161 | 162 | impl BinopCmp { 163 | fn cmp, R>(self, left: L, right: R) -> bool { 164 | match self { 165 | BinopCmp::Lt => left < right, 166 | BinopCmp::Le => left <= right, 167 | BinopCmp::Ge => left >= right, 168 | BinopCmp::Gt => left > right, 169 | } 170 | } 171 | } 172 | 173 | use std::fmt; 174 | 175 | impl fmt::Display for BinopCmp { 176 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 177 | match self { 178 | BinopCmp::Lt => write!(f, "<"), 179 | BinopCmp::Le => write!(f, "<="), 180 | BinopCmp::Ge => write!(f, ">="), 181 | BinopCmp::Gt => write!(f, ">"), 182 | } 183 | } 184 | } 185 | 186 | #[cfg(test)] 187 | mod tests { 188 | 189 | use super::*; 190 | 191 | #[test] 192 | fn possible_mutations_le() { 193 | assert_eq!( 194 | MutationBinopCmp::possible_mutations(BinopCmp::Le), 195 | vec![ 196 | MutationBinopCmp { op: BinopCmp::Lt }, 197 | MutationBinopCmp { op: BinopCmp::Ge }, 198 | MutationBinopCmp { op: BinopCmp::Gt }, 199 | ] 200 | ) 201 | } 202 | 203 | #[test] 204 | fn possible_mutations_gt() { 205 | assert_eq!( 206 | MutationBinopCmp::possible_mutations(BinopCmp::Gt), 207 | vec![ 208 | MutationBinopCmp { op: BinopCmp::Lt }, 209 | MutationBinopCmp { op: BinopCmp::Le }, 210 | MutationBinopCmp { op: BinopCmp::Ge }, 211 | ] 212 | ) 213 | } 214 | 215 | #[test] 216 | fn cmp_lt() { 217 | assert_eq!(BinopCmp::Lt.cmp(1, 2), true); 218 | assert_eq!(BinopCmp::Lt.cmp(3, 3), false); 219 | assert_eq!(BinopCmp::Lt.cmp(5, 4), false); 220 | } 221 | 222 | #[test] 223 | fn cmp_le() { 224 | assert_eq!(BinopCmp::Le.cmp(1, 2), true); 225 | assert_eq!(BinopCmp::Le.cmp(3, 3), true); 226 | assert_eq!(BinopCmp::Le.cmp(5, 4), false); 227 | } 228 | 229 | #[test] 230 | fn cmp_ge() { 231 | assert_eq!(BinopCmp::Ge.cmp(1, 2), false); 232 | assert_eq!(BinopCmp::Ge.cmp(3, 3), true); 233 | assert_eq!(BinopCmp::Ge.cmp(5, 4), true); 234 | } 235 | 236 | #[test] 237 | fn cmp_gt() { 238 | assert_eq!(BinopCmp::Gt.cmp(1, 2), false); 239 | assert_eq!(BinopCmp::Gt.cmp(3, 3), false); 240 | assert_eq!(BinopCmp::Gt.cmp(5, 4), true); 241 | } 242 | 243 | use crate::MutagenRuntimeConfig; 244 | 245 | #[test] 246 | fn mutator_cmp_gt_inactive() { 247 | assert_eq!( 248 | run( 249 | 1, 250 | 1, 251 | 2, 252 | BinopCmp::Gt, 253 | &MutagenRuntimeConfig::without_mutation() 254 | ), 255 | false 256 | ); 257 | assert_eq!( 258 | run( 259 | 1, 260 | 5, 261 | 4, 262 | BinopCmp::Gt, 263 | &MutagenRuntimeConfig::without_mutation() 264 | ), 265 | true 266 | ); 267 | } 268 | #[test] 269 | fn mutator_cmp_gt_active1() { 270 | assert_eq!( 271 | run( 272 | 1, 273 | 1, 274 | 2, 275 | BinopCmp::Gt, 276 | &MutagenRuntimeConfig::with_mutation_id(1) 277 | ), 278 | true 279 | ); 280 | assert_eq!( 281 | run( 282 | 1, 283 | 3, 284 | 3, 285 | BinopCmp::Gt, 286 | &MutagenRuntimeConfig::with_mutation_id(1) 287 | ), 288 | false 289 | ); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /mutagen-core/src/mutator/mutator_binop_eq.rs: -------------------------------------------------------------------------------- 1 | //! Mutator for binary operations `==` and `!=` 2 | 3 | use std::convert::TryFrom; 4 | use std::ops::Deref; 5 | 6 | use proc_macro2::{Span, TokenStream}; 7 | use quote::quote_spanned; 8 | use syn::spanned::Spanned; 9 | use syn::{BinOp, Expr}; 10 | 11 | use crate::comm::Mutation; 12 | use crate::transformer::transform_info::SharedTransformInfo; 13 | use crate::transformer::TransformContext; 14 | 15 | use crate::MutagenRuntimeConfig; 16 | 17 | pub fn run, R>( 18 | mutator_id: usize, 19 | left: L, 20 | right: R, 21 | original_op: BinopEq, 22 | runtime: impl Deref, 23 | ) -> bool { 24 | runtime.covered(mutator_id); 25 | let mutations = MutationBinopEq::possible_mutations(original_op); 26 | if let Some(m) = runtime.get_mutation_for_mutator(mutator_id, &mutations) { 27 | m.mutate(left, right) 28 | } else { 29 | original_op.eq(left, right) 30 | } 31 | } 32 | 33 | pub fn transform( 34 | e: Expr, 35 | transform_info: &SharedTransformInfo, 36 | context: &TransformContext, 37 | ) -> Expr { 38 | let e = match ExprBinopEq::try_from(e) { 39 | Ok(e) => e, 40 | Err(e) => return e, 41 | }; 42 | 43 | let mutator_id = transform_info.add_mutations( 44 | MutationBinopEq::possible_mutations(e.op) 45 | .iter() 46 | .map(|m| m.to_mutation(&e, context)), 47 | ); 48 | 49 | let left = &e.left; 50 | let right = &e.right; 51 | let op = e.op_tokens(); 52 | 53 | syn::parse2(quote_spanned! {e.span=> 54 | ::mutagen::mutator::mutator_binop_eq::run( 55 | #mutator_id, 56 | &(#left), 57 | &(#right), 58 | #op, 59 | ::mutagen::MutagenRuntimeConfig::get_default() 60 | ) 61 | }) 62 | .expect("transformed code invalid") 63 | } 64 | 65 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 66 | struct MutationBinopEq { 67 | op: BinopEq, 68 | } 69 | 70 | impl MutationBinopEq { 71 | fn possible_mutations(original_op: BinopEq) -> Vec { 72 | [BinopEq::Eq, BinopEq::Ne] 73 | .iter() 74 | .copied() 75 | .filter(|&op| op != original_op) 76 | .map(|op| MutationBinopEq { op }) 77 | .collect() 78 | } 79 | 80 | fn mutate, R>(self, left: L, right: R) -> bool { 81 | self.op.eq(left, right) 82 | } 83 | 84 | fn to_mutation(self, original_op: &ExprBinopEq, context: &TransformContext) -> Mutation { 85 | Mutation::new_spanned( 86 | context, 87 | "binop_eq".to_owned(), 88 | format!("{}", original_op.op), 89 | format!("{}", self.op), 90 | original_op.span, 91 | ) 92 | } 93 | } 94 | 95 | #[derive(Clone, Debug)] 96 | struct ExprBinopEq { 97 | op: BinopEq, 98 | left: Expr, 99 | right: Expr, 100 | span: Span, 101 | } 102 | 103 | impl TryFrom for ExprBinopEq { 104 | type Error = Expr; 105 | fn try_from(expr: Expr) -> Result { 106 | match expr { 107 | Expr::Binary(expr) => match expr.op { 108 | BinOp::Eq(t) => Ok(ExprBinopEq { 109 | op: BinopEq::Eq, 110 | left: *expr.left, 111 | right: *expr.right, 112 | span: t.span(), 113 | }), 114 | BinOp::Ne(t) => Ok(ExprBinopEq { 115 | op: BinopEq::Ne, 116 | left: *expr.left, 117 | right: *expr.right, 118 | span: t.span(), 119 | }), 120 | _ => Err(Expr::Binary(expr)), 121 | }, 122 | _ => Err(expr), 123 | } 124 | } 125 | } 126 | 127 | impl ExprBinopEq { 128 | fn op_tokens(&self) -> TokenStream { 129 | let mut tokens = TokenStream::new(); 130 | tokens.extend(quote_spanned!(self.span=> 131 | ::mutagen::mutator::mutator_binop_eq::BinopEq::)); 132 | tokens.extend(match self.op { 133 | BinopEq::Eq => quote_spanned!(self.span=> Eq), 134 | BinopEq::Ne => quote_spanned!(self.span=> Ne), 135 | }); 136 | tokens 137 | } 138 | } 139 | 140 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] 141 | pub enum BinopEq { 142 | Eq, 143 | Ne, 144 | } 145 | 146 | impl BinopEq { 147 | fn eq, R>(self, left: L, right: R) -> bool { 148 | match self { 149 | BinopEq::Eq => left == right, 150 | BinopEq::Ne => left != right, 151 | } 152 | } 153 | } 154 | 155 | use std::fmt; 156 | 157 | impl fmt::Display for BinopEq { 158 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 159 | match self { 160 | BinopEq::Eq => write!(f, "=="), 161 | BinopEq::Ne => write!(f, "!="), 162 | } 163 | } 164 | } 165 | 166 | #[cfg(test)] 167 | mod tests { 168 | 169 | use super::*; 170 | 171 | #[test] 172 | fn eq_inactive() { 173 | let result = run( 174 | 1, 175 | 5, 176 | 4, 177 | BinopEq::Eq, 178 | &MutagenRuntimeConfig::without_mutation(), 179 | ); 180 | assert_eq!(result, false); 181 | } 182 | #[test] 183 | fn eq_active() { 184 | let result = run( 185 | 1, 186 | 5, 187 | 4, 188 | BinopEq::Eq, 189 | &MutagenRuntimeConfig::with_mutation_id(1), 190 | ); 191 | assert_eq!(result, true); 192 | } 193 | 194 | #[test] 195 | fn ne_inactive() { 196 | let result = run( 197 | 1, 198 | 5, 199 | 4, 200 | BinopEq::Ne, 201 | &MutagenRuntimeConfig::without_mutation(), 202 | ); 203 | assert_eq!(result, true); 204 | } 205 | #[test] 206 | fn ne_active() { 207 | let result = run( 208 | 1, 209 | 5, 210 | 4, 211 | BinopEq::Ne, 212 | &MutagenRuntimeConfig::with_mutation_id(1), 213 | ); 214 | assert_eq!(result, false); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /mutagen-core/src/mutator/mutator_binop_num.rs: -------------------------------------------------------------------------------- 1 | //! Mutator for numeric binary operations `+`, `-`, `/`, `*`. 2 | 3 | use std::convert::TryFrom; 4 | use std::ops::Deref; 5 | use std::ops::{Add, Div, Mul, Sub}; 6 | 7 | use proc_macro2::Span; 8 | use quote::quote_spanned; 9 | use syn::spanned::Spanned; 10 | use syn::{BinOp, Expr}; 11 | 12 | use crate::comm::Mutation; 13 | use crate::transformer::transform_info::SharedTransformInfo; 14 | use crate::transformer::TransformContext; 15 | 16 | use crate::MutagenRuntimeConfig; 17 | 18 | pub fn run_add, R>( 19 | mutator_id: usize, 20 | left: L, 21 | right: R, 22 | runtime: impl Deref, 23 | ) -> >::Output { 24 | runtime.covered(mutator_id); 25 | if runtime.is_mutation_active(mutator_id) { 26 | left.may_sub(right) 27 | } else { 28 | left + right 29 | } 30 | } 31 | pub fn run_sub, R>( 32 | mutator_id: usize, 33 | left: L, 34 | right: R, 35 | runtime: impl Deref, 36 | ) -> >::Output { 37 | runtime.covered(mutator_id); 38 | if runtime.is_mutation_active(mutator_id) { 39 | left.may_add(right) 40 | } else { 41 | left - right 42 | } 43 | } 44 | pub fn run_mul, R>( 45 | mutator_id: usize, 46 | left: L, 47 | right: R, 48 | runtime: impl Deref, 49 | ) -> >::Output { 50 | runtime.covered(mutator_id); 51 | if runtime.is_mutation_active(mutator_id) { 52 | left.may_div(right) 53 | } else { 54 | left * right 55 | } 56 | } 57 | pub fn run_div, R>( 58 | mutator_id: usize, 59 | left: L, 60 | right: R, 61 | runtime: impl Deref, 62 | ) -> >::Output { 63 | runtime.covered(mutator_id); 64 | if runtime.is_mutation_active(mutator_id) { 65 | left.may_mul(right) 66 | } else { 67 | left / right 68 | } 69 | } 70 | 71 | pub fn transform( 72 | e: Expr, 73 | transform_info: &SharedTransformInfo, 74 | context: &TransformContext, 75 | ) -> Expr { 76 | let e = match ExprBinopNum::try_from(e) { 77 | Ok(e) => e, 78 | Err(e) => return e, 79 | }; 80 | 81 | let mutator_id = transform_info.add_mutations( 82 | MutationBinopNum::possible_mutations(e.op) 83 | .iter() 84 | .map(|m| m.to_mutation(&e, context)), 85 | ); 86 | 87 | let left = &e.left; 88 | let right = &e.right; 89 | let run_fn = match e.op { 90 | BinopNum::Add => quote_spanned! {e.span()=> run_add}, 91 | BinopNum::Sub => quote_spanned! {e.span()=> run_sub}, 92 | BinopNum::Mul => quote_spanned! {e.span()=> run_mul}, 93 | BinopNum::Div => quote_spanned! {e.span()=> run_div}, 94 | }; 95 | let op_token = e.op_token; 96 | let tmp_var = transform_info.get_next_tmp_var(op_token.span()); 97 | syn::parse2(quote_spanned! {e.span()=> 98 | { 99 | let #tmp_var = #left; 100 | if false {#tmp_var #op_token #right} else { 101 | ::mutagen::mutator::mutator_binop_num::#run_fn( 102 | #mutator_id, 103 | #tmp_var, 104 | #right, 105 | ::mutagen::MutagenRuntimeConfig::get_default() 106 | ) 107 | } 108 | } 109 | }) 110 | .expect("transformed code invalid") 111 | } 112 | 113 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 114 | struct MutationBinopNum { 115 | op: BinopNum, 116 | } 117 | 118 | impl MutationBinopNum { 119 | fn possible_mutations(original_op: BinopNum) -> Vec { 120 | match original_op { 121 | BinopNum::Add => vec![MutationBinopNum { op: BinopNum::Sub }], 122 | BinopNum::Sub => vec![MutationBinopNum { op: BinopNum::Add }], 123 | BinopNum::Mul => vec![MutationBinopNum { op: BinopNum::Div }], 124 | BinopNum::Div => vec![MutationBinopNum { op: BinopNum::Mul }], 125 | } 126 | } 127 | 128 | fn to_mutation(self, original_expr: &ExprBinopNum, context: &TransformContext) -> Mutation { 129 | Mutation::new_spanned( 130 | context, 131 | "binop_num".to_owned(), 132 | format!("{}", original_expr.op), 133 | format!("{}", self.op), 134 | original_expr.span(), 135 | ) 136 | } 137 | } 138 | 139 | #[derive(Clone, Debug)] 140 | struct ExprBinopNum { 141 | op: BinopNum, 142 | left: Expr, 143 | right: Expr, 144 | op_token: syn::BinOp, 145 | } 146 | 147 | impl TryFrom for ExprBinopNum { 148 | type Error = Expr; 149 | fn try_from(expr: Expr) -> Result { 150 | match expr { 151 | Expr::Binary(expr) => match expr.op { 152 | BinOp::Add(_) => Ok(ExprBinopNum { 153 | op: BinopNum::Add, 154 | left: *expr.left, 155 | right: *expr.right, 156 | op_token: expr.op, 157 | }), 158 | BinOp::Sub(_) => Ok(ExprBinopNum { 159 | op: BinopNum::Sub, 160 | left: *expr.left, 161 | right: *expr.right, 162 | op_token: expr.op, 163 | }), 164 | BinOp::Mul(_) => Ok(ExprBinopNum { 165 | op: BinopNum::Mul, 166 | left: *expr.left, 167 | right: *expr.right, 168 | op_token: expr.op, 169 | }), 170 | BinOp::Div(_) => Ok(ExprBinopNum { 171 | op: BinopNum::Div, 172 | left: *expr.left, 173 | right: *expr.right, 174 | op_token: expr.op, 175 | }), 176 | _ => Err(Expr::Binary(expr)), 177 | }, 178 | _ => Err(expr), 179 | } 180 | } 181 | } 182 | 183 | impl syn::spanned::Spanned for ExprBinopNum { 184 | fn span(&self) -> Span { 185 | self.op_token.span() 186 | } 187 | } 188 | 189 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] 190 | enum BinopNum { 191 | Add, 192 | Sub, 193 | Mul, 194 | Div, 195 | } 196 | 197 | use std::fmt; 198 | 199 | impl fmt::Display for BinopNum { 200 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 201 | match self { 202 | BinopNum::Add => write!(f, "+"), 203 | BinopNum::Sub => write!(f, "-"), 204 | BinopNum::Mul => write!(f, "*"), 205 | BinopNum::Div => write!(f, "/"), 206 | } 207 | } 208 | } 209 | 210 | // specification of the traits `AddToSub`, `SubToAdd`, ... 211 | // 212 | // These traits consist of a function `max_x` that panics if the operation `x` 213 | // cannot be performed due to type constraints 214 | macro_rules! binary_x_to_y { 215 | { $($may_ty:ident, $may_fn:ident, $t1:ident, $t2:ident, $t2_op:tt,)* } => { 216 | $( 217 | trait $may_ty { 218 | type Output; 219 | fn $may_fn(self, r: R) -> Self::Output; 220 | } 221 | 222 | impl $may_ty for L where L: $t1 { 223 | type Output = >::Output; 224 | default fn $may_fn(self, _r: R) -> >::Output { 225 | MutagenRuntimeConfig::get_default().optimistic_assumption_failed(); 226 | } 227 | } 228 | 229 | impl $may_ty for L 230 | where 231 | L: $t1, 232 | L: $t2, 233 | >::Output: Into<>::Output>, 234 | { 235 | fn $may_fn(self, r: R) -> Self::Output { 236 | (self $t2_op r).into() 237 | } 238 | } 239 | )* 240 | 241 | } 242 | } 243 | 244 | binary_x_to_y!( 245 | AddToSub, may_sub, Add, Sub, -, 246 | SubToAdd, may_add, Sub, Add, +, 247 | MulToDiv, may_div, Mul, Div, /, 248 | DivToMul, may_mul, Div, Mul, *, 249 | ); 250 | 251 | #[cfg(test)] 252 | mod tests { 253 | 254 | use super::*; 255 | 256 | #[test] 257 | fn sum_inactive() { 258 | let result = run_add(1, 5, 4, &MutagenRuntimeConfig::without_mutation()); 259 | assert_eq!(result, 9); 260 | } 261 | #[test] 262 | fn sum_active() { 263 | let result = run_add(1, 5, 4, &MutagenRuntimeConfig::with_mutation_id(1)); 264 | assert_eq!(result, 1); 265 | } 266 | 267 | #[test] 268 | fn str_add_inactive() { 269 | let result = run_add( 270 | 1, 271 | "x".to_string(), 272 | "y", 273 | &MutagenRuntimeConfig::without_mutation(), 274 | ); 275 | assert_eq!(&result, "xy"); 276 | } 277 | #[test] 278 | #[should_panic] 279 | fn str_add_active() { 280 | run_add( 281 | 1, 282 | "x".to_string(), 283 | "y", 284 | &MutagenRuntimeConfig::with_mutation_id(1), 285 | ); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /mutagen-core/src/mutator/mutator_lit_bool.rs: -------------------------------------------------------------------------------- 1 | //! Mutator for boolean literals. 2 | 3 | use std::convert::TryFrom; 4 | use std::ops::Deref; 5 | 6 | use proc_macro2::Span; 7 | use quote::quote_spanned; 8 | use syn::{Expr, ExprLit, Lit, LitBool}; 9 | 10 | use crate::comm::Mutation; 11 | use crate::transformer::transform_info::SharedTransformInfo; 12 | use crate::transformer::TransformContext; 13 | 14 | use crate::MutagenRuntimeConfig; 15 | 16 | pub fn run( 17 | mutator_id: usize, 18 | original_lit: bool, 19 | runtime: impl Deref, 20 | ) -> bool { 21 | runtime.covered(mutator_id); 22 | if runtime.is_mutation_active(mutator_id) { 23 | !original_lit 24 | } else { 25 | original_lit 26 | } 27 | } 28 | 29 | pub fn transform( 30 | e: Expr, 31 | transform_info: &SharedTransformInfo, 32 | context: &TransformContext, 33 | ) -> Expr { 34 | let e = match ExprLitBool::try_from(e) { 35 | Ok(e) => e, 36 | Err(e) => return e, 37 | }; 38 | 39 | let mutator_id = transform_info.add_mutation(Mutation::new_spanned( 40 | context, 41 | "lit_bool".to_owned(), 42 | format!("{:?}", e.value), 43 | format!("{:?}", !e.value), 44 | e.span, 45 | )); 46 | 47 | let value = e.value; 48 | syn::parse2(quote_spanned! {e.span=> 49 | ::mutagen::mutator::mutator_lit_bool::run( 50 | #mutator_id, 51 | #value, 52 | ::mutagen::MutagenRuntimeConfig::get_default() 53 | ) 54 | }) 55 | .expect("transformed code invalid") 56 | } 57 | 58 | #[derive(Clone, Debug)] 59 | struct ExprLitBool { 60 | value: bool, 61 | span: Span, 62 | } 63 | 64 | impl TryFrom for ExprLitBool { 65 | type Error = Expr; 66 | fn try_from(expr: Expr) -> Result { 67 | match expr { 68 | Expr::Lit(ExprLit { 69 | lit: Lit::Bool(LitBool { value, span }), 70 | .. 71 | }) => Ok(ExprLitBool { value, span }), 72 | _ => Err(expr), 73 | } 74 | } 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | 80 | use super::*; 81 | use crate::MutagenRuntimeConfig; 82 | 83 | #[test] 84 | pub fn false_inactive() { 85 | let result = run(1, false, &MutagenRuntimeConfig::without_mutation()); 86 | assert_eq!(result, false) 87 | } 88 | #[test] 89 | pub fn true_inactive() { 90 | let result = run(1, true, &MutagenRuntimeConfig::without_mutation()); 91 | assert_eq!(result, true) 92 | } 93 | #[test] 94 | pub fn false_active() { 95 | let result = run(1, false, &MutagenRuntimeConfig::with_mutation_id(1)); 96 | assert_eq!(result, true) 97 | } 98 | #[test] 99 | pub fn true_active() { 100 | let result = run(1, true, &MutagenRuntimeConfig::with_mutation_id(1)); 101 | assert_eq!(result, false) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /mutagen-core/src/mutator/mutator_lit_int.rs: -------------------------------------------------------------------------------- 1 | //! Mutator for int literals. 2 | 3 | use std::convert::TryFrom; 4 | use std::ops::Deref; 5 | 6 | use proc_macro2::Span; 7 | use quote::quote_spanned; 8 | use syn::{Expr, ExprLit, Lit, LitInt}; 9 | 10 | use crate::comm::Mutation; 11 | use crate::transformer::transform_info::SharedTransformInfo; 12 | use crate::transformer::TransformContext; 13 | 14 | use crate::MutagenRuntimeConfig; 15 | 16 | pub fn run( 17 | mutator_id: usize, 18 | original_lit: T, 19 | runtime: impl Deref, 20 | ) -> T { 21 | runtime.covered(mutator_id); 22 | let mutations = MutationLitInt::possible_mutations(original_lit.as_u128()); 23 | if let Some(m) = runtime.get_mutation_for_mutator(mutator_id, &mutations) { 24 | m.mutate(original_lit) 25 | } else { 26 | original_lit 27 | } 28 | } 29 | 30 | pub fn transform( 31 | e: Expr, 32 | transform_info: &SharedTransformInfo, 33 | context: &TransformContext, 34 | ) -> Expr { 35 | let e = match ExprLitInt::try_from(e) { 36 | Ok(e) => e, 37 | Err(e) => return e, 38 | }; 39 | 40 | let mutator_id = transform_info.add_mutations( 41 | MutationLitInt::possible_mutations(e.value) 42 | .into_iter() 43 | .map(|m| m.to_mutation(&e, context)), 44 | ); 45 | 46 | let original_lit = e.lit; 47 | syn::parse2(quote_spanned! {e.span=> 48 | ::mutagen::mutator::mutator_lit_int::run( 49 | #mutator_id, 50 | #original_lit, 51 | ::mutagen::MutagenRuntimeConfig::get_default() 52 | ) 53 | }) 54 | .expect("transformed code invalid") 55 | } 56 | 57 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 58 | enum MutationLitInt { 59 | Relative(i128), 60 | } 61 | 62 | impl MutationLitInt { 63 | fn possible_mutations(val: u128) -> Vec { 64 | let mut mutations = vec![]; 65 | if val != u128::max_value() { 66 | mutations.push(MutationLitInt::Relative(1)); 67 | } 68 | if val != 0 { 69 | mutations.push(MutationLitInt::Relative(-1)); 70 | } 71 | mutations 72 | } 73 | 74 | fn mutate(self, val: T) -> T { 75 | match self { 76 | Self::Relative(r) => IntMutable::from_u128(val.as_u128().wrapping_add(r as u128)), 77 | } 78 | } 79 | 80 | fn to_mutation(self, original_lit: &ExprLitInt, context: &TransformContext) -> Mutation { 81 | Mutation::new_spanned( 82 | context, 83 | "lit_int".to_owned(), 84 | format!("{}", original_lit.value), 85 | format!("{}", self.mutate::(original_lit.value)), 86 | original_lit.span, 87 | ) 88 | } 89 | } 90 | 91 | // trait for operations that mutate integers of any type 92 | pub trait IntMutable: Copy { 93 | fn from_u128(val: u128) -> Self; 94 | fn as_u128(self) -> u128; 95 | } 96 | 97 | // implementation for `IntMutable` for all integer types 98 | macro_rules! lit_int_mutables { 99 | { $($suf:ident, $ty:ident,)* } => { 100 | $( 101 | impl IntMutable for $ty { 102 | fn from_u128(val: u128) -> Self { 103 | val as $ty 104 | } 105 | fn as_u128(self) -> u128 { 106 | self as u128 107 | } 108 | } 109 | )* 110 | 111 | } 112 | } 113 | 114 | lit_int_mutables! { 115 | I8, i8, 116 | I16, i16, 117 | I32, i32, 118 | I64, i64, 119 | I128, i128, 120 | Isize, isize, 121 | U8, u8, 122 | U16, u16, 123 | U32, u32, 124 | U64, u64, 125 | U128, u128, 126 | Usize, usize, 127 | } 128 | 129 | #[derive(Clone, Debug)] 130 | pub struct ExprLitInt { 131 | pub value: u128, 132 | pub lit: LitInt, 133 | pub span: Span, 134 | } 135 | 136 | impl TryFrom for ExprLitInt { 137 | type Error = Expr; 138 | fn try_from(expr: Expr) -> Result { 139 | match expr { 140 | Expr::Lit(expr) => match expr.lit { 141 | Lit::Int(lit) => match lit.base10_parse::() { 142 | Ok(value) => Ok(ExprLitInt { 143 | value, 144 | span: lit.span(), 145 | lit, 146 | }), 147 | Err(_) => Err(Expr::Lit(ExprLit { 148 | lit: Lit::Int(lit), 149 | attrs: expr.attrs, 150 | })), 151 | }, 152 | _ => Err(Expr::Lit(expr)), 153 | }, 154 | _ => Err(expr), 155 | } 156 | } 157 | } 158 | 159 | #[cfg(test)] 160 | mod tests { 161 | 162 | use super::*; 163 | use crate::MutagenRuntimeConfig; 164 | 165 | #[test] 166 | pub fn mutator_lit_int_zero_inactive() { 167 | let result = run(1, 0, &MutagenRuntimeConfig::without_mutation()); 168 | assert_eq!(result, 0) 169 | } 170 | 171 | #[test] 172 | pub fn mutator_lit_int_zero_active() { 173 | let result = run(1, 0, &MutagenRuntimeConfig::with_mutation_id(1)); 174 | assert_eq!(result, 1) 175 | } 176 | 177 | #[test] 178 | fn lit_u8_suffixed_active() { 179 | let result: u8 = run(1, 1u8, &MutagenRuntimeConfig::with_mutation_id(1)); 180 | assert_eq!(result, 2); 181 | } 182 | 183 | #[test] 184 | fn possible_mutations_with_zero() { 185 | assert_eq!( 186 | MutationLitInt::possible_mutations(0), 187 | vec![MutationLitInt::Relative(1)] 188 | ); 189 | } 190 | 191 | #[test] 192 | fn possible_mutations_with_one() { 193 | assert_eq!( 194 | MutationLitInt::possible_mutations(1), 195 | vec![MutationLitInt::Relative(1), MutationLitInt::Relative(-1)] 196 | ); 197 | } 198 | 199 | #[test] 200 | fn possible_mutations_with_max_value() { 201 | assert_eq!( 202 | MutationLitInt::possible_mutations(u128::max_value()), 203 | vec![MutationLitInt::Relative(-1)] 204 | ); 205 | } 206 | 207 | #[test] 208 | fn mutate_relative1() { 209 | assert_eq!(MutationLitInt::Relative(1).mutate(2), 3) 210 | } 211 | 212 | #[test] 213 | fn mutate_relative_neg1() { 214 | assert_eq!(MutationLitInt::Relative(-1).mutate(2), 1) 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /mutagen-core/src/mutator/mutator_lit_str.rs: -------------------------------------------------------------------------------- 1 | //! Mutator for str literals. 2 | 3 | use std::convert::TryFrom; 4 | use std::ops::Deref; 5 | 6 | use proc_macro2::Span; 7 | use quote::quote_spanned; 8 | use syn::{Expr, Lit, LitStr}; 9 | 10 | use crate::comm::Mutation; 11 | use crate::transformer::transform_info::SharedTransformInfo; 12 | use crate::transformer::TransformContext; 13 | 14 | use crate::MutagenRuntimeConfig; 15 | 16 | pub fn run( 17 | mutator_id: usize, 18 | original_lit: &'static str, 19 | mutations: &[&'static str], 20 | runtime: &impl Deref, 21 | ) -> &'static str { 22 | runtime.covered(mutator_id); 23 | if let Some(m) = runtime.get_mutation_for_mutator(mutator_id, &mutations) { 24 | m 25 | } else { 26 | original_lit 27 | } 28 | } 29 | 30 | pub fn transform( 31 | e: Expr, 32 | transform_info: &SharedTransformInfo, 33 | context: &TransformContext, 34 | ) -> Expr { 35 | let e = match ExprLitStr::try_from(e) { 36 | Ok(e) => e, 37 | Err(e) => return e, 38 | }; 39 | 40 | let possible_mutations = MutationLitStr::possible_mutations(e.clone().value); 41 | let mutations: Vec<_> = possible_mutations 42 | .iter() 43 | .map(|x| x.mutate(&e.clone().value)) 44 | .collect(); 45 | 46 | let mutator_id = transform_info.add_mutations( 47 | possible_mutations 48 | .into_iter() 49 | .map(|m| m.to_mutation(&e, context)), 50 | ); 51 | 52 | let original_lit = e.lit.value(); 53 | 54 | syn::parse2(quote_spanned! {e.span=> 55 | ::mutagen::mutator::mutator_lit_str::run( 56 | #mutator_id, 57 | #original_lit, 58 | &[#(&#mutations),*], // Expands to `&[mutations[0], mutations[1], ..., [mutations[n]]]` 59 | &::mutagen::MutagenRuntimeConfig::get_default() 60 | ) 61 | }) 62 | .expect("transformed code invalid") 63 | } 64 | 65 | #[derive(Clone, Debug, PartialEq, Eq)] 66 | enum MutationLitStr { 67 | Clear, 68 | Set(&'static str), 69 | Append(char), 70 | Prepend(char), 71 | } 72 | 73 | impl MutationLitStr { 74 | fn possible_mutations(val: String) -> Vec { 75 | let mut mutations = vec![]; 76 | if val.is_empty() { 77 | mutations.push(Self::Set("A")) 78 | } else { 79 | mutations.push(Self::Clear); 80 | mutations.push(Self::Prepend('-')); 81 | mutations.push(Self::Append('-')); 82 | } 83 | mutations 84 | } 85 | 86 | fn mutate(&self, val: &str) -> String { 87 | match self { 88 | Self::Clear => "".to_string(), 89 | Self::Set(string) => string.to_string(), 90 | Self::Append(char) => { 91 | let mut new = val.to_string(); 92 | new.push(*char); 93 | new 94 | } 95 | Self::Prepend(char) => { 96 | let mut new = val.to_string(); 97 | new.insert(0, *char); 98 | new 99 | } 100 | } 101 | } 102 | 103 | fn to_mutation(&self, original_lit: &ExprLitStr, context: &TransformContext) -> Mutation { 104 | Mutation::new_spanned( 105 | context, 106 | "lit_str".to_owned(), 107 | original_lit.value.to_string(), 108 | self.mutate(&original_lit.value).to_string(), 109 | original_lit.span, 110 | ) 111 | } 112 | } 113 | 114 | #[derive(Clone, Debug)] 115 | pub struct ExprLitStr { 116 | pub value: String, 117 | pub lit: LitStr, 118 | pub span: Span, 119 | } 120 | 121 | impl TryFrom for ExprLitStr { 122 | type Error = Expr; 123 | fn try_from(expr: Expr) -> Result { 124 | match expr { 125 | Expr::Lit(expr) => match expr.lit { 126 | Lit::Str(lit) => Ok(ExprLitStr { 127 | value: lit.value(), 128 | span: lit.span(), 129 | lit, 130 | }), 131 | _ => Err(Expr::Lit(expr)), 132 | }, 133 | _ => Err(expr), 134 | } 135 | } 136 | } 137 | 138 | #[cfg(test)] 139 | mod tests { 140 | 141 | use super::*; 142 | use crate::MutagenRuntimeConfig; 143 | 144 | #[test] 145 | pub fn mutator_lit_str_empty_inactive() { 146 | let result = run(1, "", &["A"], &&MutagenRuntimeConfig::without_mutation()); 147 | assert_eq!(result, "") 148 | } 149 | 150 | #[test] 151 | pub fn mutator_lit_str_non_empty_inactive() { 152 | let result = run( 153 | 1, 154 | "ABCD", 155 | &["", "-ABCD", "ABCD-"], 156 | &&MutagenRuntimeConfig::without_mutation(), 157 | ); 158 | assert_eq!(result, "ABCD") 159 | } 160 | 161 | #[test] 162 | pub fn mutator_lit_str_non_empty_active_1() { 163 | let result = run( 164 | 1, 165 | "a", 166 | &["", "-ABCD", "ABCD-"], 167 | &&MutagenRuntimeConfig::with_mutation_id(1), 168 | ); 169 | assert_eq!(result, "") 170 | } 171 | 172 | #[test] 173 | pub fn mutator_lit_str_non_empty_active_2() { 174 | let result = run( 175 | 1, 176 | "a", 177 | &["", "-ABCD", "ABCD-"], 178 | &&MutagenRuntimeConfig::with_mutation_id(2), 179 | ); 180 | assert_eq!(result, "-ABCD") 181 | } 182 | 183 | #[test] 184 | pub fn mutator_lit_str_non_empty_active_3() { 185 | let result = run( 186 | 1, 187 | "a", 188 | &["", "-ABCD", "ABCD-"], 189 | &&MutagenRuntimeConfig::with_mutation_id(3), 190 | ); 191 | assert_eq!(result, "ABCD-") 192 | } 193 | 194 | #[test] 195 | pub fn mutator_lit_str_empty_active_1() { 196 | let result = run(1, "", &["A"], &&MutagenRuntimeConfig::with_mutation_id(1)); 197 | assert_eq!(result, "A") 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /mutagen-core/src/mutator/mutator_stmt_call.rs: -------------------------------------------------------------------------------- 1 | //! Mutator for removing statements that only consist of a method or function call. 2 | 3 | use std::convert::TryFrom; 4 | use std::ops::Deref; 5 | 6 | use proc_macro2::{Span, TokenStream}; 7 | use quote::quote_spanned; 8 | use quote::ToTokens; 9 | use syn::spanned::Spanned; 10 | use syn::{Expr, Stmt}; 11 | 12 | use crate::comm::Mutation; 13 | use crate::transformer::transform_info::SharedTransformInfo; 14 | use crate::transformer::TransformContext; 15 | 16 | use crate::MutagenRuntimeConfig; 17 | 18 | pub fn should_run(mutator_id: usize, runtime: impl Deref) -> bool { 19 | runtime.covered(mutator_id); 20 | // should run if mutation is inactive 21 | !runtime.is_mutation_active(mutator_id) 22 | } 23 | 24 | pub fn transform( 25 | s: Stmt, 26 | transform_info: &SharedTransformInfo, 27 | context: &TransformContext, 28 | ) -> Stmt { 29 | let s = match StmtCall::try_from(s) { 30 | Ok(s) => s, 31 | Err(s) => return s, 32 | }; 33 | 34 | let mutator_id = transform_info.add_mutation(Mutation::new_spanned( 35 | context, 36 | "stmt_call".to_owned(), 37 | context 38 | .original_stmt 39 | .to_token_stream() 40 | .to_string() 41 | .replace("\n", " "), 42 | "".to_owned(), 43 | s.span, 44 | )); 45 | 46 | let call = &s.call; 47 | 48 | syn::parse2(quote_spanned! {s.span=> 49 | if ::mutagen::mutator::mutator_stmt_call::should_run( 50 | #mutator_id, 51 | ::mutagen::MutagenRuntimeConfig::get_default() 52 | ) 53 | { 54 | #call; 55 | } else { 56 | ::mutagen::mutator::mutator_stmt_call::stmt_call_to_none() 57 | } 58 | }) 59 | .expect("transformed code invalid") 60 | } 61 | 62 | #[derive(Debug, Clone)] 63 | struct StmtCall { 64 | call: TokenStream, 65 | span: Span, 66 | } 67 | 68 | impl TryFrom for StmtCall { 69 | type Error = Stmt; 70 | fn try_from(stmt: Stmt) -> Result { 71 | match stmt { 72 | Stmt::Semi(Expr::MethodCall(call), _) => Ok(StmtCall { 73 | span: call.span(), 74 | call: call.into_token_stream(), 75 | }), 76 | Stmt::Semi(Expr::Call(call), _) => Ok(StmtCall { 77 | span: call.span(), 78 | call: call.into_token_stream(), 79 | }), 80 | _ => Err(stmt), 81 | } 82 | } 83 | } 84 | 85 | /// a trait for optimistically removing a statement containing a method- or function call. 86 | /// 87 | /// This operation is optimistic, since the statement could have the type `!` and can be used in surprising contexts: 88 | /// 89 | /// * `let x = {f(return y);}` 90 | /// * `let x = {std::process::abort();}` 91 | /// 92 | /// Above examples compile and it is not possible to remove the statements without introducing compiler errors. 93 | pub trait StmtCallToNone { 94 | fn stmt_call_to_none() -> Self; 95 | } 96 | 97 | impl StmtCallToNone for T { 98 | default fn stmt_call_to_none() -> Self { 99 | MutagenRuntimeConfig::get_default().optimistic_assumption_failed(); 100 | } 101 | } 102 | 103 | impl StmtCallToNone for () { 104 | fn stmt_call_to_none() {} 105 | } 106 | 107 | pub fn stmt_call_to_none() -> T { 108 | ::stmt_call_to_none() 109 | } 110 | 111 | #[cfg(test)] 112 | mod tests { 113 | 114 | use super::*; 115 | 116 | #[test] 117 | fn stmt_inactive() { 118 | let result = should_run(1, &MutagenRuntimeConfig::without_mutation()); 119 | assert_eq!(result, true); 120 | } 121 | #[test] 122 | fn stmt_active() { 123 | let result = should_run(1, &MutagenRuntimeConfig::with_mutation_id(1)); 124 | assert_eq!(result, false); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /mutagen-core/src/mutator/mutator_unop_not.rs: -------------------------------------------------------------------------------- 1 | //! Mutator for binary operation `+`. 2 | 3 | use std::convert::TryFrom; 4 | use std::ops::Deref; 5 | use std::ops::Not; 6 | 7 | use proc_macro2::Span; 8 | use quote::quote_spanned; 9 | use syn::spanned::Spanned; 10 | use syn::{Expr, UnOp}; 11 | 12 | use crate::comm::Mutation; 13 | use crate::transformer::transform_info::SharedTransformInfo; 14 | use crate::transformer::TransformContext; 15 | 16 | use crate::MutagenRuntimeConfig; 17 | 18 | pub fn run( 19 | mutator_id: usize, 20 | val: T, 21 | runtime: impl Deref, 22 | ) -> ::Output { 23 | runtime.covered(mutator_id); 24 | if runtime.is_mutation_active(mutator_id) { 25 | val.may_none() 26 | } else { 27 | !val 28 | } 29 | } 30 | 31 | pub fn transform( 32 | e: Expr, 33 | transform_info: &SharedTransformInfo, 34 | context: &TransformContext, 35 | ) -> Expr { 36 | let e = match ExprUnopNot::try_from(e) { 37 | Ok(e) => e, 38 | Err(e) => return e, 39 | }; 40 | 41 | let mutator_id = transform_info.add_mutation(Mutation::new_spanned( 42 | context, 43 | "unop_not".to_owned(), 44 | "!".to_owned(), 45 | "".to_owned(), 46 | e.span(), 47 | )); 48 | 49 | let expr = &e.expr; 50 | let op_token = e.op_token; 51 | let tmp_var = transform_info.get_next_tmp_var(op_token.span()); 52 | syn::parse2(quote_spanned! {e.span()=> 53 | { 54 | let #tmp_var = #expr; 55 | if false {!#tmp_var} else { 56 | ::mutagen::mutator::mutator_unop_not::run( 57 | #mutator_id, 58 | #tmp_var, 59 | ::mutagen::MutagenRuntimeConfig::get_default() 60 | ) 61 | } 62 | } 63 | }) 64 | .expect("transformed code invalid") 65 | } 66 | 67 | #[derive(Clone, Debug)] 68 | struct ExprUnopNot { 69 | expr: Expr, 70 | op_token: syn::UnOp, 71 | } 72 | 73 | impl TryFrom for ExprUnopNot { 74 | type Error = Expr; 75 | fn try_from(expr: Expr) -> Result { 76 | match expr { 77 | Expr::Unary(expr) => match expr.op { 78 | UnOp::Not(_) => Ok(ExprUnopNot { 79 | expr: *expr.expr, 80 | op_token: expr.op, 81 | }), 82 | _ => Err(Expr::Unary(expr)), 83 | }, 84 | e => Err(e), 85 | } 86 | } 87 | } 88 | 89 | impl syn::spanned::Spanned for ExprUnopNot { 90 | fn span(&self) -> Span { 91 | self.op_token.span() 92 | } 93 | } 94 | 95 | /// trait that is used to optimistically remove a negation `!` from an expression 96 | /// 97 | /// This trait provides a function `may_none` that passes the input value unchanged 98 | /// If the value cannot be converted to the output type of the negation using `Into`, the optimistic assumption fails. 99 | pub trait NotToNone { 100 | type Output; 101 | // do nothing 102 | fn may_none(self) -> Self::Output; 103 | } 104 | 105 | impl NotToNone for T 106 | where 107 | T: Not, 108 | { 109 | type Output = ::Output; 110 | 111 | default fn may_none(self) -> ::Output { 112 | MutagenRuntimeConfig::get_default().optimistic_assumption_failed(); 113 | } 114 | } 115 | 116 | impl NotToNone for T 117 | where 118 | T: Not, 119 | T: Into<::Output>, 120 | { 121 | fn may_none(self) -> Self::Output { 122 | self.into() 123 | } 124 | } 125 | 126 | /// types for testing the optimistic mutator that removes the negation 127 | #[cfg(any(test, feature = "self_test"))] 128 | pub mod optimistic_types { 129 | 130 | use std::ops::Not; 131 | 132 | #[derive(Debug, PartialEq)] 133 | pub struct TypeWithNotOtherOutput(); 134 | #[derive(Debug, PartialEq)] 135 | pub struct TypeWithNotTarget(); 136 | 137 | impl Not for TypeWithNotOtherOutput { 138 | type Output = TypeWithNotTarget; 139 | 140 | fn not(self) -> ::Output { 141 | TypeWithNotTarget() 142 | } 143 | } 144 | } 145 | 146 | #[cfg(test)] 147 | mod tests { 148 | 149 | use super::optimistic_types::*; 150 | use super::*; 151 | 152 | #[test] 153 | fn boolnot_inactive() { 154 | // input is true, but will be negated by non-active mutator 155 | let result = run(1, true, &MutagenRuntimeConfig::without_mutation()); 156 | assert_eq!(result, false); 157 | } 158 | #[test] 159 | fn boolnot_active() { 160 | let result = run(1, true, &MutagenRuntimeConfig::with_mutation_id(1)); 161 | assert_eq!(result, true); 162 | } 163 | 164 | #[test] 165 | fn optimistic_incorrect_inactive() { 166 | let result = run( 167 | 1, 168 | TypeWithNotOtherOutput(), 169 | &MutagenRuntimeConfig::without_mutation(), 170 | ); 171 | assert_eq!(result, TypeWithNotTarget()); 172 | } 173 | #[test] 174 | #[should_panic] 175 | fn optimistic_incorrect_active() { 176 | run( 177 | 1, 178 | TypeWithNotOtherOutput(), 179 | &MutagenRuntimeConfig::with_mutation_id(1), 180 | ); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /mutagen-core/src/transformer/ast_inspect.rs: -------------------------------------------------------------------------------- 1 | //! a collection of functions for extracting information from ast-types. 2 | 3 | /// check if an expression has numeric type. 4 | /// 5 | /// This is implemented via a heuristic. An expression has an numeric type if: 6 | /// * it is a numeric literal 7 | /// * it is an binary arithmetic- or bit-operation that has an integer expression on the left side 8 | /// * it is an unary operation with an numeric expression 9 | /// * it is a reference to a numeric expression. This lets us count `*&1` as numeric expression. 10 | /// * it is a block that ends in a numeric expression. This lets us count {...; 1} as numeric expression. 11 | /// * it is a if expression with an numeric expression as one of the cases 12 | pub fn is_num_expr(e: &syn::Expr) -> bool { 13 | match e { 14 | syn::Expr::Lit(expr) => match expr.lit { 15 | syn::Lit::Int(_) => true, 16 | syn::Lit::Byte(_) => true, 17 | syn::Lit::Float(_) => true, 18 | _ => false, 19 | }, 20 | syn::Expr::Binary(expr) => match expr.op { 21 | syn::BinOp::Add(_) => is_num_expr(&expr.left), 22 | syn::BinOp::Sub(_) => is_num_expr(&expr.left), 23 | syn::BinOp::Mul(_) => is_num_expr(&expr.left), 24 | syn::BinOp::Div(_) => is_num_expr(&expr.left), 25 | syn::BinOp::Rem(_) => is_num_expr(&expr.left), 26 | syn::BinOp::BitAnd(_) => is_num_expr(&expr.left), 27 | syn::BinOp::BitOr(_) => is_num_expr(&expr.left), 28 | syn::BinOp::BitXor(_) => is_num_expr(&expr.left), 29 | syn::BinOp::Shl(_) => is_num_expr(&expr.left), 30 | syn::BinOp::Shr(_) => is_num_expr(&expr.left), 31 | _ => false, 32 | }, 33 | syn::Expr::Unary(expr) => is_num_expr(&expr.expr), 34 | syn::Expr::Reference(expr) => is_num_expr(&expr.expr), 35 | syn::Expr::Paren(expr) => is_num_expr(&expr.expr), 36 | syn::Expr::Block(expr) => is_num_block(&expr.block), 37 | syn::Expr::If(expr) => is_num_expr_if(&expr), 38 | _ => false, 39 | } 40 | } 41 | 42 | fn is_num_expr_if(expr: &syn::ExprIf) -> bool { 43 | is_num_block(&expr.then_branch) 44 | || match &expr.else_branch { 45 | Some((_, else_expr)) => is_num_expr(else_expr), 46 | _ => false, 47 | } 48 | } 49 | 50 | fn is_num_block(block: &syn::Block) -> bool { 51 | match block.stmts.last() { 52 | Some(stmt) => is_num_stmt(&stmt), 53 | _ => false, 54 | } 55 | } 56 | fn is_num_stmt(stmt: &syn::Stmt) -> bool { 57 | match stmt { 58 | syn::Stmt::Expr(expr) => is_num_expr(&expr), 59 | _ => false, 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | 66 | use super::*; 67 | use syn::parse_quote; 68 | 69 | #[test] 70 | fn num_expr_lit_int() { 71 | let tt = parse_quote! {1}; 72 | 73 | assert!(is_num_expr(&tt)); 74 | } 75 | 76 | #[test] 77 | fn num_expr_add_of_lit_int() { 78 | let tt = parse_quote! {1 + 2}; 79 | 80 | assert!(is_num_expr(&tt)); 81 | } 82 | 83 | #[test] 84 | fn num_expr_neg_one() { 85 | let tt = parse_quote! {-1}; 86 | 87 | assert!(is_num_expr(&tt)); 88 | } 89 | 90 | #[test] 91 | fn num_expr_bit_not_one() { 92 | let tt = parse_quote! {!1}; 93 | 94 | assert!(is_num_expr(&tt)); 95 | } 96 | 97 | #[test] 98 | fn num_expr_variable() { 99 | let tt = parse_quote! {x}; 100 | 101 | assert!(!is_num_expr(&tt)); 102 | } 103 | 104 | #[test] 105 | fn num_expr_multiple_plus_lit_int() { 106 | let tt = parse_quote! {1+2+3}; 107 | 108 | assert!(is_num_expr(&tt)); 109 | } 110 | 111 | #[test] 112 | fn num_expr_multiple_plus_left_is_var() { 113 | let tt = parse_quote! {x+2+3}; 114 | 115 | assert!(!is_num_expr(&tt)); 116 | } 117 | 118 | #[test] 119 | fn num_expr_deref_ref_lit_int() { 120 | let tt = parse_quote! {*&1}; 121 | 122 | assert!(is_num_expr(&tt)); 123 | } 124 | 125 | #[test] 126 | fn num_expr_lit_float() { 127 | let tt = parse_quote! {1.5}; 128 | 129 | assert!(is_num_expr(&tt)); 130 | } 131 | 132 | #[test] 133 | fn num_expr_lit_byte() { 134 | let tt = parse_quote! {b'a'}; 135 | 136 | assert!(is_num_expr(&tt)); 137 | } 138 | 139 | #[test] 140 | fn num_expr_lit_str() { 141 | let tt = parse_quote! {"a"}; 142 | 143 | assert!(!is_num_expr(&tt)); 144 | } 145 | 146 | #[test] 147 | fn num_expr_lit_bool() { 148 | let tt = parse_quote! {true}; 149 | 150 | assert!(!is_num_expr(&tt)); 151 | } 152 | 153 | #[test] 154 | fn num_expr_bool_and() { 155 | let tt = parse_quote! {true && false}; 156 | 157 | assert!(!is_num_expr(&tt)); 158 | } 159 | 160 | #[test] 161 | fn num_expr_bitand_vars() { 162 | let tt = parse_quote! {x & y}; 163 | 164 | assert!(!is_num_expr(&tt)); 165 | } 166 | 167 | #[test] 168 | fn num_expr_bitand_lit_int() { 169 | let tt = parse_quote! {1 & 2}; 170 | 171 | assert!(is_num_expr(&tt)); 172 | } 173 | 174 | #[test] 175 | fn num_expr_shl_lit_int() { 176 | let tt = parse_quote! {1 << 3}; 177 | 178 | assert!(is_num_expr(&tt)); 179 | } 180 | 181 | #[test] 182 | fn num_expr_shr_lit_int() { 183 | let tt = parse_quote! {1 >> 3}; 184 | 185 | assert!(is_num_expr(&tt)); 186 | } 187 | 188 | #[test] 189 | fn num_expr_not_shift() { 190 | let tt = parse_quote! {!(1 >> 3)}; 191 | 192 | assert!(is_num_expr(&tt), format!("{:#?}", tt)); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /mutagen-core/src/transformer/mutate_args.rs: -------------------------------------------------------------------------------- 1 | //! parse arguments for the `#[mutate]` attribute and gather all information necessary to transform the source code. 2 | //! 3 | //! Please refer to the customization documentation about the format of arguments. 4 | 5 | use super::arg_ast::{ArgAstList, ArgFn}; 6 | use proc_macro2::TokenStream; 7 | 8 | #[derive(PartialEq, Eq, Debug)] 9 | pub struct ArgOptions { 10 | pub conf: Conf, 11 | pub transformers: Transformers, 12 | } 13 | 14 | #[derive(PartialEq, Eq, Debug)] 15 | pub enum Transformers { 16 | All, 17 | Only(TransformerList), 18 | Not(TransformerList), 19 | } 20 | 21 | #[derive(PartialEq, Eq, Debug)] 22 | pub enum Conf { 23 | Global, 24 | Local(LocalConf), 25 | } 26 | 27 | #[derive(PartialEq, Eq, Debug, Default)] 28 | pub struct LocalConf { 29 | pub expected_mutations: Option, 30 | } 31 | 32 | #[derive(PartialEq, Eq, Debug)] 33 | pub struct TransformerList { 34 | pub transformers: Vec, 35 | } 36 | 37 | impl Default for ArgOptions { 38 | fn default() -> Self { 39 | Self { 40 | conf: Conf::Global, 41 | transformers: Transformers::All, 42 | } 43 | } 44 | } 45 | 46 | impl ArgOptions { 47 | pub fn parse(args: TokenStream) -> Result { 48 | let mut options: Self = Default::default(); 49 | 50 | let ast = ArgAstList::parse_list(args)?; 51 | if let Some(conf) = ast.find_named_arg("conf")? { 52 | options.conf = Conf::parse(conf)?; 53 | } 54 | if let Some(transformers_arg) = ast.find_named_arg("mutators")? { 55 | match &*transformers_arg.name { 56 | "only" => { 57 | options.transformers = Transformers::parse_only(&transformers_arg.args)?; 58 | } 59 | "not" => { 60 | options.transformers = Transformers::parse_not(&transformers_arg.args)?; 61 | } 62 | _ => return Err(()), 63 | } 64 | } 65 | Ok(options) 66 | } 67 | } 68 | 69 | impl Conf { 70 | fn parse(conf: &ArgFn) -> Result { 71 | match &*conf.name { 72 | "local" => { 73 | let expected_mutations = conf.args.find_named_arg("expected_mutations")?; 74 | let expected_mutations = expected_mutations 75 | .map(|arg| arg.name.parse::()) 76 | .transpose() 77 | .map_err(|_| ())?; 78 | Ok(Conf::Local(LocalConf { expected_mutations })) 79 | } 80 | "global" => Ok(Conf::Global), 81 | _ => Err(()), 82 | } 83 | } 84 | } 85 | 86 | impl TransformerList { 87 | fn parse(ast: &ArgAstList) -> Result { 88 | let transformers = ast 89 | .0 90 | .iter() 91 | .map(|t| { 92 | let t = t.expect_fn_ref()?; 93 | if !t.args.0.is_empty() { 94 | return Err(()); 95 | } 96 | Ok(t.name.clone()) 97 | }) 98 | .collect::, ()>>()?; 99 | Ok(Self { transformers }) 100 | } 101 | } 102 | 103 | impl Transformers { 104 | fn parse_only(ast: &ArgAstList) -> Result { 105 | Ok(Transformers::Only(TransformerList::parse(ast)?)) 106 | } 107 | 108 | fn parse_not(ast: &ArgAstList) -> Result { 109 | Ok(Transformers::Not(TransformerList::parse(ast)?)) 110 | } 111 | } 112 | 113 | #[cfg(test)] 114 | mod tests { 115 | 116 | use super::*; 117 | use proc_macro2::TokenStream; 118 | use std::str::FromStr; 119 | 120 | #[test] 121 | fn config_for_empty_args() { 122 | let input = TokenStream::new(); 123 | 124 | let parsed = ArgOptions::parse(input); 125 | 126 | let expected = Ok(ArgOptions::default()); 127 | 128 | assert_eq!(expected, parsed); 129 | } 130 | 131 | #[test] 132 | fn config_local() { 133 | let input = TokenStream::from_str("conf = local").unwrap(); 134 | 135 | let parsed = ArgOptions::parse(input); 136 | 137 | assert!(parsed.is_ok()); 138 | let parsed = parsed.unwrap(); 139 | 140 | let expected_conf_local = Conf::Local(LocalConf::default()); 141 | assert_eq!(parsed.conf, expected_conf_local); 142 | assert_eq!(parsed.transformers, Transformers::All); 143 | } 144 | 145 | #[test] 146 | fn config_local_single_mutator() { 147 | let input = TokenStream::from_str("conf = local, mutators = only(binop_add)").unwrap(); 148 | 149 | let parsed = ArgOptions::parse(input); 150 | 151 | assert!(parsed.is_ok()); 152 | let parsed = parsed.unwrap(); 153 | 154 | let expected_conf_local = Conf::Local(LocalConf::default()); 155 | assert_eq!(parsed.conf, expected_conf_local); 156 | 157 | let expected_transformers = Transformers::Only(TransformerList { 158 | transformers: vec!["binop_add".to_owned()], 159 | }); 160 | assert_eq!(parsed.transformers, expected_transformers); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /mutagen-core/src/transformer/transform_context.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Default)] 2 | pub struct TransformContext { 3 | pub impl_name: Option, 4 | pub fn_name: Option, 5 | pub original_stmt: Option, 6 | pub original_expr: Option, 7 | } 8 | -------------------------------------------------------------------------------- /mutagen-core/src/transformer/transform_info.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use std::fs::{create_dir_all, File}; 3 | use std::iter; 4 | use std::sync::{Arc, Mutex, MutexGuard}; 5 | 6 | use proc_macro2::Span; 7 | 8 | use super::mutate_args::LocalConf; 9 | use crate::comm; 10 | use crate::comm::{BakedMutation, Mutation}; 11 | 12 | lazy_static! { 13 | static ref GLOBAL_TRANSFORM_INFO: SharedTransformInfo = Default::default(); 14 | } 15 | 16 | #[derive(Default)] 17 | pub struct SharedTransformInfo(Arc>); 18 | 19 | /// Contains information about all mutations inserted into the code under test 20 | /// 21 | /// This struct is used to collect the mutations during transformation. 22 | /// After running all transformers, this struct contains all mutators 23 | /// and their mutations inserted into the code 24 | #[derive(Debug)] 25 | pub struct MutagenTransformInfo { 26 | mutations: Vec, 27 | mutagen_file: Option, 28 | expected_mutations: Option, 29 | tmp_var_id: usize, 30 | } 31 | 32 | impl Default for MutagenTransformInfo { 33 | fn default() -> Self { 34 | Self { 35 | mutations: vec![], 36 | mutagen_file: None, 37 | expected_mutations: None, 38 | tmp_var_id: 0, 39 | } 40 | } 41 | } 42 | 43 | impl MutagenTransformInfo { 44 | fn with_default_mutagen_file(&mut self) { 45 | // open file only once 46 | if self.mutagen_file.is_none() { 47 | let mutagen_filepath = comm::get_mutations_file().unwrap(); 48 | let mutagen_dir = mutagen_filepath.parent().unwrap(); 49 | if !mutagen_dir.exists() { 50 | create_dir_all(&mutagen_dir).unwrap(); 51 | } 52 | let mutagen_file = File::create(&mutagen_filepath) 53 | .unwrap_or_else(|_| panic!("unable to open file {:?}", &mutagen_filepath)); 54 | 55 | self.mutagen_file = Some(mutagen_file); 56 | } 57 | } 58 | 59 | /// add a mutation and return the id used for it, also writes the mutation to the global file. 60 | fn add_mutation(&mut self, mutation: Mutation, mutator_id: usize) -> usize { 61 | let mut_id = 1 + self.mutations.len(); 62 | let mutation = mutation.with_id(mut_id, mutator_id); 63 | 64 | // write the mutation if file was configured 65 | if let Some(mutagen_file) = &mut self.mutagen_file { 66 | comm::append_item(mutagen_file, &mutation).expect("unable to write to mutagen file"); 67 | } 68 | 69 | // add mutation to list 70 | self.mutations.push(mutation); 71 | 72 | // return next mutation id 73 | mut_id 74 | } 75 | 76 | fn get_num_mutations(&self) -> usize { 77 | self.mutations.len() 78 | } 79 | 80 | fn get_next_mutation_id(&self) -> usize { 81 | self.mutations.len() + 1 82 | } 83 | 84 | fn check_mutations(&mut self) { 85 | if let Some(expected_mutations) = self.expected_mutations { 86 | let actual_mutations = self.mutations.len(); 87 | if expected_mutations != actual_mutations { 88 | panic!( 89 | "expected {} mutations but inserted {}", 90 | expected_mutations, actual_mutations 91 | ); 92 | } 93 | } 94 | } 95 | 96 | fn get_next_tmp_var(&mut self, span: Span) -> syn::Ident { 97 | self.tmp_var_id += 1; 98 | syn::Ident::new(&format!("__mutagen_tmp_{}", self.tmp_var_id), span) 99 | } 100 | } 101 | 102 | impl SharedTransformInfo { 103 | fn lock_transform_info(&self) -> MutexGuard { 104 | self.0.lock().unwrap() 105 | } 106 | 107 | fn new(transform_info: MutagenTransformInfo) -> SharedTransformInfo { 108 | SharedTransformInfo(Arc::new(Mutex::new(transform_info))) 109 | } 110 | 111 | pub fn global_info() -> Self { 112 | GLOBAL_TRANSFORM_INFO 113 | .lock_transform_info() 114 | .with_default_mutagen_file(); 115 | GLOBAL_TRANSFORM_INFO.clone_shared() 116 | } 117 | 118 | pub fn local_info(conf: LocalConf) -> Self { 119 | let mut transform_info = MutagenTransformInfo::default(); 120 | if let Some(n) = conf.expected_mutations { 121 | transform_info.expected_mutations = Some(n); 122 | } 123 | Self::new(transform_info) 124 | } 125 | 126 | pub fn add_mutation(&self, mutation: Mutation) -> usize { 127 | self.add_mutations(iter::once(mutation)) 128 | } 129 | 130 | pub fn add_mutations(&self, mutations: impl IntoIterator) -> usize { 131 | let mut transform_info = self.lock_transform_info(); 132 | 133 | let mutator_id = transform_info.get_next_mutation_id(); 134 | 135 | // add all mutations within a single lock and return the first id 136 | for mutation in mutations.into_iter() { 137 | transform_info.add_mutation(mutation, mutator_id); 138 | } 139 | mutator_id 140 | } 141 | 142 | pub fn clone_shared(&self) -> Self { 143 | Self(Arc::clone(&self.0)) 144 | } 145 | 146 | pub fn get_num_mutations(&self) -> usize { 147 | self.lock_transform_info().get_num_mutations() 148 | } 149 | 150 | pub fn check_mutations(&self) { 151 | self.lock_transform_info().check_mutations() 152 | } 153 | 154 | pub fn get_next_tmp_var(&self, span: Span) -> syn::Ident { 155 | self.lock_transform_info().get_next_tmp_var(span) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /mutagen-runner/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-mutagen" 3 | version = "0.2.1" 4 | authors = ["Andre Bogus ", "Samuel Pilz "] 5 | edition = "2018" 6 | license = "Apache-2.0/MIT" 7 | 8 | [dependencies] 9 | json = "0.12.4" 10 | anyhow = "1.0.57" 11 | wait-timeout = "0.2.0" 12 | serde_json = "1.0.68" 13 | mutagen-core = { path = "../mutagen-core"} 14 | console = "0.15.0" 15 | humantime = "2.1.0" 16 | structopt = "0.3.23" 17 | 18 | [badges] 19 | travis-ci = { repository = "llogiq/mutagen", branch = "master" } 20 | 21 | [lib] 22 | doctest = false 23 | -------------------------------------------------------------------------------- /mutagen-runner/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod progress; 2 | mod progress_bar; 3 | mod test_bin; 4 | 5 | pub use progress::Progress; 6 | pub use test_bin::{TestBin, TestBinTested}; 7 | -------------------------------------------------------------------------------- /mutagen-runner/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use std::collections::HashMap; 3 | use std::env; 4 | use std::fs::File; 5 | use std::io::BufWriter; 6 | use std::path::PathBuf; 7 | use std::process; 8 | use std::process::{Command, Stdio}; 9 | use std::str; 10 | use std::time::Instant; 11 | 12 | use cargo_mutagen::*; 13 | use mutagen_core::comm; 14 | use mutagen_core::comm::{BakedMutation, CoverageCollection, MutagenReport, MutantStatus}; 15 | 16 | fn main() { 17 | if let Err(err) = run() { 18 | eprintln!(); 19 | eprintln!("Error!"); 20 | eprintln!("{}", err); 21 | process::exit(1); 22 | } 23 | } 24 | 25 | use structopt::StructOpt; 26 | #[derive(StructOpt, Debug)] 27 | struct Options { 28 | /// Space-separated list of features to activate 29 | #[structopt(long, name = "FEATURES")] 30 | features: Option, 31 | 32 | /// Activate all available features 33 | #[structopt(long)] 34 | all_features: bool, 35 | 36 | /// Package to run tests for 37 | #[structopt(long, name = "SPEC")] 38 | package: Option, 39 | 40 | /// Test all packages in the workspace 41 | #[structopt(long)] 42 | workspace: bool, 43 | } 44 | 45 | fn run() -> Result<()> { 46 | let mutagen_start = Instant::now(); 47 | 48 | // drop "mutagen" arg in cargo-subcommand mode 49 | let mut args = env::args(); 50 | if env::var("CARGO").is_ok() { 51 | // we're invoked by cargo, drop the first arg 52 | args.next(); 53 | } 54 | let opt = Options::from_iter(args); 55 | 56 | // build the testsuites and collect mutations 57 | let test_bins = compile_tests(&opt)?; 58 | if test_bins.is_empty() { 59 | bail!("no test executable(s) found"); 60 | } 61 | let mutations = read_mutations()?; 62 | let num_mutations = mutations.len(); 63 | 64 | let mut progress = Progress::new(mutations.len()); 65 | progress.summary_compile(mutations.len(), test_bins.len())?; 66 | 67 | // run all test-binaries without mutations and collect coverge 68 | progress.section_testsuite_unmutated(test_bins.len())?; 69 | 70 | let test_bins = test_bins 71 | .iter() 72 | .enumerate() 73 | .map(|(i, e)| TestBin::new(e, i)) 74 | .filter_map(|bin| { 75 | bin.run_test(&mut progress, &mutations) 76 | .map(|bin| Some(bin).filter(|bin| bin.coveres_any_mutation())) 77 | .transpose() 78 | }) 79 | .collect::>>()?; 80 | 81 | let coverage = CoverageCollection::merge(num_mutations, test_bins.iter().map(|b| &b.coverage)); 82 | progress.summary_testsuite_unmutated(coverage.num_covered())?; 83 | 84 | // run the mutations on the test-suites 85 | progress.section_mutants()?; 86 | let mutagen_report = run_mutations(&mut progress, &test_bins, mutations, &coverage)?; 87 | 88 | progress.section_summary()?; 89 | 90 | // final report 91 | mutagen_report.print_survived(); 92 | mutagen_report.summary().print(); 93 | 94 | progress.finish(mutagen_start.elapsed())?; 95 | 96 | Ok(()) 97 | } 98 | 99 | /// run all mutations on all test-executables 100 | fn run_mutations( 101 | progress: &mut Progress, 102 | test_bins: &[TestBinTested], 103 | mutations: Vec, 104 | coverage: &CoverageCollection, 105 | ) -> Result { 106 | let mut mutagen_report = MutagenReport::new(); 107 | 108 | for m in mutations { 109 | let mutant_status = if coverage.is_covered(m.id()) { 110 | progress.start_mutation_covered(&m)?; 111 | 112 | // run all test binaries 113 | let mut mutant_status = MutantStatus::Survived; 114 | for bin in test_bins { 115 | mutant_status = bin.check_mutant(&m)?; 116 | if mutant_status != MutantStatus::Survived { 117 | break; 118 | } 119 | } 120 | progress.finish_mutation(mutant_status)?; 121 | 122 | mutant_status 123 | } else { 124 | progress.skip_mutation_uncovered(&m)?; 125 | MutantStatus::NotCovered 126 | }; 127 | mutagen_report.add_mutation_result(m, mutant_status); 128 | } 129 | 130 | Ok(mutagen_report) 131 | } 132 | 133 | /// build all tests and collect test-suite executables 134 | fn compile_tests(opt: &Options) -> Result> { 135 | let mut tests: Vec = Vec::new(); 136 | 137 | let mut feature_args: Vec<&str> = vec![]; 138 | if let Some(f) = &opt.features { 139 | feature_args.extend(&["--features", f]); 140 | } 141 | if opt.all_features { 142 | feature_args.push("--all-features"); 143 | } 144 | if let Some(p) = &opt.package { 145 | feature_args.extend(&["--package", p]); 146 | } 147 | if opt.workspace { 148 | feature_args.push("--workspace"); 149 | } 150 | 151 | // execute `cargo test --no-run --message-format=json` and collect output 152 | let compile_out = Command::new("cargo") 153 | .args(&["test", "--no-run", "--message-format=json"]) 154 | .args(&feature_args) 155 | .stderr(Stdio::inherit()) 156 | .output()?; 157 | if !compile_out.status.success() { 158 | bail!("`cargo test --no-run` returned non-zero exit status"); 159 | } 160 | let compile_stdout = str::from_utf8(&compile_out.stdout)?; 161 | 162 | // each line is a json-value, we want to extract the test-executables 163 | // these are compiler artifacts that have set `test:true` in the profile 164 | let current_dir = std::env::current_dir()?; 165 | for line in compile_stdout.lines() { 166 | let msg_json = json::parse(line)?; 167 | if msg_json["reason"].as_str() == Some("compiler-artifact") 168 | && msg_json["profile"]["test"].as_bool() == Some(true) 169 | { 170 | let mut test_exe: PathBuf = msg_json["executable"].as_str().unwrap().to_string().into(); 171 | 172 | // if the executable is found in the `deps` folder, execute it from there instead 173 | let test_exe_in_deps_dir = test_exe 174 | .parent() 175 | .unwrap() 176 | .join("deps") 177 | .join(test_exe.file_name().unwrap()); 178 | if test_exe_in_deps_dir.exists() { 179 | test_exe = test_exe_in_deps_dir 180 | } 181 | 182 | // try to make path relative to current path 183 | test_exe = test_exe 184 | .strip_prefix(¤t_dir) 185 | .map(|x| x.to_owned()) 186 | .unwrap_or(test_exe); 187 | 188 | tests.push(test_exe); 189 | } 190 | } 191 | Ok(tests) 192 | } 193 | 194 | /// read all mutations from the given file 195 | /// 196 | /// This functions gets the file that describes all mutations performed on the target program and ensures that it exists. 197 | /// The list of mutations is also preserved 198 | fn read_mutations() -> Result> { 199 | let mutations_file = comm::get_mutations_file()?; 200 | if !mutations_file.exists() { 201 | bail!( 202 | "file `target/mutagen/mutations` is not found\n\ 203 | maybe there are no mutations defined or the attribute `#[mutate]` is not enabled" 204 | ) 205 | } 206 | 207 | let mutations = comm::read_items::(&mutations_file)?; 208 | 209 | // write the collected mutations 210 | let mutations_map = mutations 211 | .iter() 212 | .map(|m| (m.id(), m.as_ref())) 213 | .collect::>(); 214 | let mutations_writer = BufWriter::new(File::create(comm::get_mutations_file_json()?)?); 215 | serde_json::to_writer(mutations_writer, &mutations_map)?; 216 | 217 | Ok(mutations) 218 | } 219 | -------------------------------------------------------------------------------- /mutagen-runner/src/progress.rs: -------------------------------------------------------------------------------- 1 | //! Custom implementation of printing progress of the cargo-mutagen runner. 2 | //! 3 | //! This module contains a progress bar similar to the one cargo uses. 4 | //! If the output is not a terminal or the terminal is too small, no progress bar is shown. 5 | //! The progress bar tries to be adaptive as possible and only uses a single line in every case. 6 | //! 7 | //! The main challenges is to be able to continue writing to the line above the progress bar. 8 | //! The output to the terminal should look identical to piped output but contains a progress bar. 9 | 10 | use anyhow::Result; 11 | 12 | use std::path::Path; 13 | use std::time::Duration; 14 | 15 | use mutagen_core::comm::{BakedMutation, MutantStatus}; 16 | 17 | use super::progress_bar::{ProgressBar, ProgressBarState}; 18 | 19 | /// Print progress during mutation testing 20 | pub struct Progress { 21 | num_mutations: usize, 22 | num_covered: usize, 23 | tested_mutations: usize, 24 | bar: ProgressBar, 25 | } 26 | 27 | impl Progress { 28 | pub fn new(num_mutations: usize) -> Self { 29 | Self { 30 | num_mutations, 31 | num_covered: 0, 32 | tested_mutations: 0, 33 | bar: ProgressBar::new(), 34 | } 35 | } 36 | 37 | /// Print summary information after the compilation of the test binaries. 38 | pub fn summary_compile(&mut self, num_mutations: usize, num_testsuites: usize) -> Result<()> { 39 | self.bar.println("")?; 40 | self.bar 41 | .println(&format!("Total mutations: {}", num_mutations))?; 42 | 43 | self.bar.set_total(num_testsuites); 44 | 45 | Ok(()) 46 | } 47 | 48 | /// Start the section that runs the test suites unmutated. 49 | pub fn section_testsuite_unmutated(&mut self, num_tests: usize) -> Result<()> { 50 | self.bar.println("")?; 51 | self.bar.println(&format!("Run {} tests", num_tests))?; 52 | Ok(()) 53 | } 54 | 55 | /// start the section of test-runs for each mutation 56 | pub fn section_mutants(&mut self) -> Result<()> { 57 | self.bar.println("")?; 58 | self.bar 59 | .println(&format!("Test {} Mutants", self.num_mutations))?; 60 | Ok(()) 61 | } 62 | 63 | /// start the section of the 64 | pub fn section_summary(&mut self) -> Result<()> { 65 | self.bar.println("")?; 66 | self.bar.clear_bar()?; 67 | Ok(()) 68 | } 69 | 70 | /// indicate the start of a run of a single testsuite without mutations 71 | pub fn start_testsuite_unmutated(&mut self, bin: &Path, id: usize) -> Result<()> { 72 | let log_string = format!("{} ... ", bin.display()); 73 | self.bar.print(log_string)?; 74 | 75 | if self.bar.shows_progress() { 76 | let bar = ProgressBarState { 77 | action: "Run Tests", 78 | current: id + 1, 79 | action_details: format!("{}", bin.display()), 80 | }; 81 | 82 | self.bar.set_state(bar)?; 83 | } 84 | 85 | Ok(()) 86 | } 87 | 88 | /// indicate the end of a run of a single testsuite and display the result. 89 | pub fn finish_testsuite_unmutated(&mut self, ok: bool, num_covered: usize) -> Result<()> { 90 | if ok && num_covered > 0 { 91 | self.bar.println(&format!( 92 | "ok ({}/{} covered)", 93 | num_covered, self.num_mutations 94 | )) 95 | } else if ok && num_covered == 0 { 96 | self.bar.println("ok (NOTHING COVERED)") 97 | } else { 98 | self.bar.println("FAILED") 99 | } 100 | } 101 | 102 | /// print a summary after the testsuites have been run, especially coverage information. 103 | pub fn summary_testsuite_unmutated(&mut self, num_covered: usize) -> Result<()> { 104 | self.num_covered = num_covered; 105 | self.bar.set_total(num_covered); 106 | 107 | self.bar.println("")?; 108 | self.bar.println(&format!( 109 | "Mutations covered: {}/{}", 110 | self.num_covered, self.num_mutations 111 | )) 112 | } 113 | 114 | /// indicate that a test-run of a covered mutation begins. 115 | /// 116 | /// The information about the mutation is logged to the console. 117 | /// A call to `finish_mutation` should follow a call to this function 118 | pub fn start_mutation_covered(&mut self, m: &BakedMutation) -> Result<()> { 119 | let mut mutant_log_string = mutation_log_string(m); 120 | mutant_log_string += " ... "; 121 | 122 | self.bar.print(mutant_log_string)?; 123 | 124 | self.tested_mutations += 1; 125 | 126 | // write progress bar 127 | if self.bar.shows_progress() { 128 | let action_details = format!( 129 | "{}{}", 130 | m.source_file().display(), 131 | m.context_description_in_brackets(), 132 | ); 133 | let bar = ProgressBarState { 134 | action: "Test Mutants", 135 | current: self.tested_mutations, 136 | action_details, 137 | }; 138 | self.bar.set_state(bar)?; 139 | } 140 | 141 | Ok(()) 142 | } 143 | 144 | pub fn skip_mutation_uncovered(&mut self, m: &BakedMutation) -> Result<()> { 145 | self.bar.println(&format!( 146 | "{} ... {}", 147 | mutation_log_string(m), 148 | MutantStatus::NotCovered 149 | )) 150 | } 151 | 152 | /// indicate that a mutation started with `start_mutation` has been finished. 153 | /// 154 | /// The status is printed and progress bar is updated 155 | pub fn finish_mutation(&mut self, status: MutantStatus) -> Result<()> { 156 | self.bar.println(&format!("{}", status))?; 157 | Ok(()) 158 | } 159 | 160 | /// indicate that mutation-testing is finished 161 | /// 162 | /// clears the progress-bar 163 | pub fn finish(mut self, mutagen_time: Duration) -> Result<()> { 164 | let rounded_time = Duration::from_secs(mutagen_time.as_secs()); 165 | self.bar.println(&format!( 166 | "Total time: {}", 167 | ::humantime::format_duration(rounded_time) 168 | ))?; 169 | self.bar.finish()?; 170 | Ok(()) 171 | } 172 | } 173 | 174 | /// Generate a string used for logging 175 | fn mutation_log_string(m: &BakedMutation) -> String { 176 | format!( 177 | "{}: {}, {}, at {}@{}{}", 178 | m.id(), 179 | m.mutator_name(), 180 | m.mutation_description(), 181 | m.source_file().display(), 182 | m.location_in_file(), 183 | m.context_description_in_brackets(), 184 | ) 185 | } 186 | -------------------------------------------------------------------------------- /mutagen-runner/src/progress_bar.rs: -------------------------------------------------------------------------------- 1 | //! Custom implementation of printing progress of the cargo-mutagen runner. 2 | //! 3 | //! This module contains a progress bar similar to the one cargo uses. 4 | //! If the output is not a terminal or the terminal is too small, no progress bar is shown. 5 | //! The progress bar tries to be adaptive as possible and only uses a single line in every case. 6 | //! 7 | //! The main challenges is to be able to continue writing to the line above the progress bar. 8 | 9 | use anyhow::Result; 10 | use console::Term; 11 | use std::io::Write; 12 | 13 | /// Print progress during mutation testing 14 | pub struct ProgressBar { 15 | term: Term, 16 | term_width: usize, 17 | show_progress: bool, 18 | total: usize, 19 | current_log_str: Option, 20 | current_bar_state: Option, 21 | } 22 | 23 | pub struct ProgressBarState { 24 | pub action: &'static str, 25 | pub current: usize, 26 | pub action_details: String, 27 | } 28 | 29 | impl ProgressBar { 30 | pub fn new() -> Self { 31 | let term = Term::stdout(); 32 | let term_width = term.size().1 as usize; 33 | let show_progress = term.is_term() && term_width > 20; 34 | 35 | Self { 36 | term, 37 | term_width, 38 | show_progress, 39 | total: 0, 40 | current_log_str: None, 41 | current_bar_state: None, 42 | } 43 | } 44 | 45 | /// re-sets to total actions to perform 46 | pub fn set_total(&mut self, total: usize) { 47 | self.total = total; 48 | } 49 | 50 | pub fn shows_progress(&self) -> bool { 51 | self.show_progress 52 | } 53 | 54 | /// prints some text to stdout. 55 | /// 56 | /// If the progress bar is shown, the text is printed above the progress bar. 57 | /// The next call to `println` will continue writing the line started by this function. 58 | pub fn print(&mut self, s: String) -> Result<()> { 59 | if self.show_progress { 60 | self.term.clear_line()?; 61 | } 62 | 63 | // TODO: allow multiple print-calls and newlines 64 | assert!( 65 | self.current_log_str.is_none(), 66 | "consecutive calls to ProgressBar::print are currently not supported" 67 | ); 68 | assert!( 69 | !s.contains('\n'), 70 | "newlines are currently not supported in ProgressBar::print" 71 | ); 72 | 73 | write!(&self.term, "{}", &s)?; 74 | 75 | if self.show_progress { 76 | writeln!(&self.term)?; 77 | 78 | self.write_progress_bar()?; 79 | 80 | self.current_log_str = Some(s); 81 | } 82 | Ok(()) 83 | } 84 | 85 | /// prints a line to stdout. 86 | /// 87 | /// If the progress bar is shown, the line is printed above the progress bar. 88 | pub fn println(&mut self, s: &str) -> Result<()> { 89 | if self.show_progress { 90 | self.term.clear_line()?; 91 | 92 | if let Some(log_str) = self.current_log_str.take() { 93 | let log_str_lines = 1 + (log_str.len() + 1) / self.term_width; 94 | self.term.clear_last_lines(log_str_lines)?; 95 | writeln!(&self.term, "{}{}", log_str, s)?; 96 | } else { 97 | writeln!(&self.term, "{}", s)?; 98 | } 99 | 100 | self.write_progress_bar()?; 101 | } else { 102 | writeln!(&self.term, "{}", s)?; 103 | } 104 | 105 | self.current_log_str = None; 106 | 107 | Ok(()) 108 | } 109 | 110 | /// clears the progress bar 111 | pub fn clear_bar(&mut self) -> Result<()> { 112 | if self.current_bar_state.take().is_some() { 113 | self.term.clear_line()?; 114 | } 115 | Ok(()) 116 | } 117 | 118 | /// finish the progress bar 119 | /// 120 | /// clears the progress-indicator 121 | pub fn finish(self) -> Result<()> { 122 | if self.show_progress { 123 | self.term.clear_line()?; 124 | writeln!(&self.term)?; 125 | } 126 | Ok(()) 127 | } 128 | 129 | /// updates the state of the progress bar and draws the new state if the progress is shown 130 | pub fn set_state(&mut self, bar: ProgressBarState) -> Result<()> { 131 | if self.show_progress { 132 | self.current_bar_state = Some(bar); 133 | self.write_progress_bar()?; 134 | } 135 | Ok(()) 136 | } 137 | 138 | fn write_progress_bar(&self) -> Result<()> { 139 | if let Some(bar) = &self.current_bar_state { 140 | if self.total == 0 { 141 | return Ok(()); 142 | } 143 | let current_total_string = format!("{}/{}", bar.current, self.total); 144 | let action_name = console::style(format!("{:>12}", bar.action)).bold(); 145 | 146 | let main_part_len = self.term_width.min(80); 147 | 148 | // construct progress bar 149 | let bar_width = main_part_len - 18 - current_total_string.len(); 150 | let mut bar_pos = bar_width * bar.current / self.total; 151 | if bar_pos == bar_width { 152 | bar_pos -= 1; 153 | } 154 | let bar1 = "=".repeat(bar_pos); 155 | let bar2 = " ".repeat(bar_width - bar_pos - 1); 156 | 157 | // print status details right to progress bar, if there is space for it 158 | let mut action_details = bar.action_details.to_owned(); 159 | action_details = format!(": {}", action_details); 160 | let space_after_main_bar = self.term_width - main_part_len; 161 | if space_after_main_bar < 10 { 162 | action_details = "".to_owned(); 163 | } else if space_after_main_bar < action_details.len() { 164 | action_details = format!("{:.*}...", space_after_main_bar - 5, action_details); 165 | } 166 | 167 | write!( 168 | &self.term, 169 | "{} [{}>{}] {}{}\r", 170 | action_name, bar1, bar2, current_total_string, action_details 171 | )?; 172 | } 173 | 174 | Ok(()) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /mutagen-runner/src/test_bin.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use std::fs; 3 | use std::io::Write; 4 | use std::path::Path; 5 | use std::process::{Command, Stdio}; 6 | use std::time::{Duration, Instant}; 7 | 8 | use wait_timeout::ChildExt; 9 | 10 | use mutagen_core::comm::{self, BakedMutation, CoverageCollection, CoverageHit, MutantStatus}; 11 | 12 | use super::Progress; 13 | 14 | /// wrapper around a test-binary that can be executed 15 | #[derive(Debug)] 16 | pub struct TestBin<'a> { 17 | id: usize, 18 | pub bin_path: &'a Path, 19 | } 20 | 21 | // wrapper around a test-binary, which has been run already and its runtime has been timed. 22 | #[derive(Debug)] 23 | pub struct TestBinTested<'a> { 24 | test_bin: TestBin<'a>, 25 | exe_time: Duration, 26 | pub coverage: CoverageCollection, 27 | } 28 | 29 | impl<'a> TestBin<'a> { 30 | pub fn new(bin_path: &'a Path, id: usize) -> Self { 31 | Self { id, bin_path } 32 | } 33 | 34 | // run the test and record the covered mutators and the time required to run the tests. 35 | pub fn run_test( 36 | self, 37 | progress: &mut Progress, 38 | mutations: &[BakedMutation], 39 | ) -> Result> { 40 | let num_mutations = mutations.len(); 41 | let test_start = Instant::now(); 42 | 43 | progress.start_testsuite_unmutated(self.bin_path, self.id)?; 44 | 45 | ::std::io::stdout().flush()?; 46 | 47 | // run test suite 48 | let mut command = Command::new(self.bin_path); 49 | command.env("MUTAGEN_MODE", "coverage"); 50 | command.env("MUTAGEN_NUM_MUTATIONS", format!("{}", num_mutations)); 51 | command.env("MUTAGEN_TESTSUITE", &self.bin_path); 52 | command.stdout(Stdio::null()); 53 | let mut test_run = command.spawn()?; 54 | let status = test_run.wait()?; 55 | let exe_time = test_start.elapsed(); 56 | 57 | let success = status.success(); 58 | 59 | if !success { 60 | bail!("test suite fails. Retry after `cargo test` succeeds"); 61 | } 62 | 63 | // read the coverage-file for this testsuite and delete it afterwards 64 | let coverage = { 65 | let coverage_file = comm::get_coverage_file()?; 66 | if !coverage_file.exists() { 67 | // no coverage file means that no mutations has been covered 68 | CoverageCollection::new_empty(num_mutations) 69 | } else { 70 | let coverage_hits = comm::read_items::(&coverage_file)?; 71 | // delete coverage file after the execution of this testsuite 72 | fs::remove_file(coverage_file)?; 73 | 74 | CoverageCollection::from_coverage_hits(num_mutations, &coverage_hits, mutations) 75 | } 76 | }; 77 | 78 | progress.finish_testsuite_unmutated(success, coverage.num_covered())?; 79 | 80 | Ok(TestBinTested { 81 | test_bin: self, 82 | coverage, 83 | exe_time, 84 | }) 85 | } 86 | } 87 | 88 | impl<'a> TestBinTested<'a> { 89 | /// Checks if any mutation is covered. 90 | /// 91 | /// Returns false, if no mutation is covered by the testsuite 92 | pub fn coveres_any_mutation(&self) -> bool { 93 | self.coverage.num_covered() != 0 94 | } 95 | 96 | pub fn check_mutant(&self, mutation: &BakedMutation) -> Result { 97 | // run command and wait for its output 98 | let mut command = Command::new(self.test_bin.bin_path); 99 | command.env("MUTATION_ID", mutation.id().to_string()); 100 | command.stdout(Stdio::null()); 101 | command.stderr(Stdio::null()); 102 | let mut test_run = command.spawn()?; 103 | 104 | let wait_time = 5 * self.exe_time + Duration::from_millis(500); 105 | let timeout = test_run.wait_timeout(wait_time)?; 106 | 107 | Ok(match timeout { 108 | Some(status) => { 109 | if status.success() { 110 | MutantStatus::Survived 111 | } else { 112 | MutantStatus::Killed(status.code()) 113 | } 114 | } 115 | None => { 116 | test_run.kill()?; 117 | MutantStatus::Timeout 118 | } 119 | }) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /mutagen-selftest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mutagen-selftest" 3 | version = "0.2.0" 4 | authors = ["Andre Bogus ", "Samuel Pilz "] 5 | edition = "2018" 6 | license = "Apache-2.0/MIT" 7 | 8 | [dev-dependencies] 9 | mutagen = { path = "../mutagen" } 10 | mutagen-core = { path = "../mutagen-core", features = ["self_test"] } 11 | 12 | [lib] 13 | doctest = false 14 | -------------------------------------------------------------------------------- /mutagen-selftest/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | mod mutator; 4 | mod runtime_config; 5 | mod test_not_mutated; 6 | -------------------------------------------------------------------------------- /mutagen-selftest/src/mutator.rs: -------------------------------------------------------------------------------- 1 | mod test_binop_bit; 2 | mod test_binop_bool; 3 | mod test_binop_cmp; 4 | mod test_binop_eq; 5 | mod test_binop_num; 6 | mod test_lit_bool; 7 | mod test_lit_int; 8 | mod test_lit_str; 9 | mod test_stmt_call; 10 | mod test_unop_not; 11 | -------------------------------------------------------------------------------- /mutagen-selftest/src/mutator/test_binop_bit.rs: -------------------------------------------------------------------------------- 1 | mod test_and_i32 { 2 | 3 | use ::mutagen::mutate; 4 | use ::mutagen::MutagenRuntimeConfig; 5 | 6 | // simple function that sums two values 7 | #[mutate(conf = local(expected_mutations = 2), mutators = only(binop_bit))] 8 | fn and_u32() -> u32 { 9 | 0b10 & 0b11 10 | } 11 | #[test] 12 | fn and_u32_inactive() { 13 | MutagenRuntimeConfig::test_without_mutation(|| { 14 | assert_eq!(and_u32(), 0b10); 15 | }) 16 | } 17 | #[test] 18 | fn sum_u32_active1() { 19 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 20 | assert_eq!(and_u32(), 0b11); 21 | }) 22 | } 23 | #[test] 24 | fn sum_u32_active2() { 25 | MutagenRuntimeConfig::test_with_mutation_id(2, || { 26 | assert_eq!(and_u32(), 0b01); 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mutagen-selftest/src/mutator/test_binop_bool.rs: -------------------------------------------------------------------------------- 1 | mod test_and { 2 | use ::mutagen::mutate; 3 | use ::mutagen::MutagenRuntimeConfig; 4 | 5 | // and-operation using 2 closures 6 | #[mutate(conf = local)] 7 | fn and(left: impl Fn() -> bool, right: impl Fn() -> bool) -> bool { 8 | left() && right() 9 | } 10 | #[test] 11 | fn and_inactive() { 12 | MutagenRuntimeConfig::test_without_mutation(|| { 13 | assert_eq!(and(|| true, || true), true); 14 | assert_eq!(and(|| false, || true), false); 15 | assert_eq!(and(|| true, || false), false); 16 | assert_eq!(and(|| false, || false), false); 17 | }) 18 | } 19 | #[test] 20 | fn and_active1() { 21 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 22 | assert_eq!(and(|| true, || true), true); 23 | assert_eq!(and(|| false, || true), true); 24 | assert_eq!(and(|| true, || false), true); 25 | assert_eq!(and(|| false, || false), false); 26 | }) 27 | } 28 | #[test] 29 | fn and_short_circuit_inactive() { 30 | MutagenRuntimeConfig::test_without_mutation(|| { 31 | assert_eq!(and(|| false, || panic!()), false); 32 | }) 33 | } 34 | #[test] 35 | #[should_panic] 36 | fn and_short_circuit_active() { 37 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 38 | and(|| false, || panic!()); 39 | }) 40 | } 41 | } 42 | mod test_or { 43 | use ::mutagen::mutate; 44 | use ::mutagen::MutagenRuntimeConfig; 45 | 46 | // and-operation using 2 closures 47 | #[mutate(conf = local)] 48 | fn or(left: impl Fn() -> bool, right: impl Fn() -> bool) -> bool { 49 | left() || right() 50 | } 51 | #[test] 52 | fn or_inactive() { 53 | MutagenRuntimeConfig::test_without_mutation(|| { 54 | assert_eq!(or(|| true, || true), true); 55 | assert_eq!(or(|| false, || true), true); 56 | assert_eq!(or(|| true, || false), true); 57 | assert_eq!(or(|| false, || false), false); 58 | }) 59 | } 60 | #[test] 61 | fn or_active1() { 62 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 63 | assert_eq!(or(|| true, || true), true); 64 | assert_eq!(or(|| false, || true), false); 65 | assert_eq!(or(|| true, || false), false); 66 | assert_eq!(or(|| false, || false), false); 67 | }) 68 | } 69 | #[test] 70 | fn or_short_circuit_inactive() { 71 | MutagenRuntimeConfig::test_without_mutation(|| { 72 | assert_eq!(or(|| true, || panic!()), true); 73 | }) 74 | } 75 | #[test] 76 | #[should_panic] 77 | fn or_short_circuit_active() { 78 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 79 | or(|| true, || panic!()); 80 | }) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /mutagen-selftest/src/mutator/test_binop_cmp.rs: -------------------------------------------------------------------------------- 1 | mod test_lt { 2 | 3 | use ::mutagen::mutate; 4 | use ::mutagen::MutagenRuntimeConfig; 5 | 6 | // simple comparison 7 | #[mutate(conf = local(expected_mutations = 3), mutators = only(binop_cmp))] 8 | fn lt(left: i32, right: i32) -> bool { 9 | left < right 10 | } 11 | #[test] 12 | fn lt_inactive() { 13 | MutagenRuntimeConfig::test_without_mutation(|| { 14 | assert_eq!(lt(1, 2), true); 15 | assert_eq!(lt(3, 3), false); 16 | assert_eq!(lt(5, 4), false); 17 | }) 18 | } 19 | // replace with <= 20 | #[test] 21 | fn lt_active1() { 22 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 23 | assert_eq!(lt(1, 2), true); 24 | assert_eq!(lt(3, 3), true); 25 | assert_eq!(lt(5, 4), false); 26 | }) 27 | } 28 | // replace with >= 29 | #[test] 30 | fn lt_active2() { 31 | MutagenRuntimeConfig::test_with_mutation_id(2, || { 32 | assert_eq!(lt(1, 2), false); 33 | assert_eq!(lt(3, 3), true); 34 | assert_eq!(lt(5, 4), true); 35 | }) 36 | } 37 | // replace with > 38 | #[test] 39 | fn sum_u32_active3() { 40 | MutagenRuntimeConfig::test_with_mutation_id(3, || { 41 | assert_eq!(lt(1, 2), false); 42 | assert_eq!(lt(3, 3), false); 43 | assert_eq!(lt(5, 4), true); 44 | }) 45 | } 46 | } 47 | 48 | mod test_le { 49 | 50 | use ::mutagen::mutate; 51 | use ::mutagen::MutagenRuntimeConfig; 52 | 53 | // simple comparison 54 | #[mutate(conf = local(expected_mutations = 3), mutators = only(binop_cmp))] 55 | fn le(left: i32, right: i32) -> bool { 56 | left <= right 57 | } 58 | #[test] 59 | fn le_inactive() { 60 | MutagenRuntimeConfig::test_without_mutation(|| { 61 | assert_eq!(le(1, 2), true); 62 | assert_eq!(le(3, 3), true); 63 | assert_eq!(le(5, 4), false); 64 | }) 65 | } 66 | // replace with < 67 | #[test] 68 | fn le_active1() { 69 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 70 | assert_eq!(le(1, 2), true); 71 | assert_eq!(le(3, 3), false); 72 | assert_eq!(le(5, 4), false); 73 | }) 74 | } 75 | // replace with >= 76 | #[test] 77 | fn le_active2() { 78 | MutagenRuntimeConfig::test_with_mutation_id(2, || { 79 | assert_eq!(le(1, 2), false); 80 | assert_eq!(le(3, 3), true); 81 | assert_eq!(le(5, 4), true); 82 | }) 83 | } 84 | // replace with > 85 | #[test] 86 | fn le_active3() { 87 | MutagenRuntimeConfig::test_with_mutation_id(3, || { 88 | assert_eq!(le(1, 2), false); 89 | assert_eq!(le(3, 3), false); 90 | assert_eq!(le(5, 4), true); 91 | }) 92 | } 93 | } 94 | 95 | mod test_ge { 96 | 97 | use ::mutagen::mutate; 98 | use ::mutagen::MutagenRuntimeConfig; 99 | 100 | // simpge comparison 101 | #[mutate(conf = local(expected_mutations = 3), mutators = only(binop_cmp))] 102 | fn ge(left: i32, right: i32) -> bool { 103 | left >= right 104 | } 105 | #[test] 106 | fn ge_inactive() { 107 | MutagenRuntimeConfig::test_without_mutation(|| { 108 | assert_eq!(ge(1, 2), false); 109 | assert_eq!(ge(3, 3), true); 110 | assert_eq!(ge(5, 4), true); 111 | }) 112 | } 113 | // replace with < 114 | #[test] 115 | fn ge_active1() { 116 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 117 | assert_eq!(ge(1, 2), true); 118 | assert_eq!(ge(3, 3), false); 119 | assert_eq!(ge(5, 4), false); 120 | }) 121 | } 122 | // replace with <= 123 | #[test] 124 | fn ge_active2() { 125 | MutagenRuntimeConfig::test_with_mutation_id(2, || { 126 | assert_eq!(ge(1, 2), true); 127 | assert_eq!(ge(3, 3), true); 128 | assert_eq!(ge(5, 4), false); 129 | }) 130 | } 131 | // replace with > 132 | #[test] 133 | fn ge_active3() { 134 | MutagenRuntimeConfig::test_with_mutation_id(3, || { 135 | assert_eq!(ge(1, 2), false); 136 | assert_eq!(ge(3, 3), false); 137 | assert_eq!(ge(5, 4), true); 138 | }) 139 | } 140 | } 141 | 142 | mod test_gt { 143 | 144 | use ::mutagen::mutate; 145 | use ::mutagen::MutagenRuntimeConfig; 146 | 147 | // simple comparison 148 | #[mutate(conf = local(expected_mutations = 3), mutators = only(binop_cmp))] 149 | fn gt(left: i32, right: i32) -> bool { 150 | left > right 151 | } 152 | #[test] 153 | fn gt_inactive() { 154 | MutagenRuntimeConfig::test_without_mutation(|| { 155 | assert_eq!(gt(1, 2), false); 156 | assert_eq!(gt(3, 3), false); 157 | assert_eq!(gt(5, 4), true); 158 | }) 159 | } 160 | // replace with < 161 | #[test] 162 | fn gt_active1() { 163 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 164 | assert_eq!(gt(1, 2), true); 165 | assert_eq!(gt(3, 3), false); 166 | assert_eq!(gt(5, 4), false); 167 | }) 168 | } 169 | // replace with <= 170 | #[test] 171 | fn gt_active2() { 172 | MutagenRuntimeConfig::test_with_mutation_id(2, || { 173 | assert_eq!(gt(1, 2), true); 174 | assert_eq!(gt(3, 3), true); 175 | assert_eq!(gt(5, 4), false); 176 | }) 177 | } 178 | // replace with >= 179 | #[test] 180 | fn gt_active3() { 181 | MutagenRuntimeConfig::test_with_mutation_id(3, || { 182 | assert_eq!(gt(1, 2), false); 183 | assert_eq!(gt(3, 3), true); 184 | assert_eq!(gt(5, 4), true); 185 | }) 186 | } 187 | } 188 | 189 | mod test_cmp_nocopy { 190 | use ::mutagen::mutate; 191 | use ::mutagen::MutagenRuntimeConfig; 192 | 193 | // simple comparison. This test checks that the comparison does not consume the input strings 194 | #[mutate(conf = local(expected_mutations = 3), mutators = only(binop_cmp))] 195 | fn max(left: String, right: String) -> String { 196 | if left > right { 197 | left 198 | } else { 199 | right 200 | } 201 | } 202 | 203 | #[test] 204 | fn max_inactive() { 205 | MutagenRuntimeConfig::test_without_mutation(|| { 206 | assert_eq!(max("a".to_owned(), "b".to_owned()), "b".to_owned()); 207 | assert_eq!(max("b".to_owned(), "a".to_owned()), "b".to_owned()); 208 | }) 209 | } 210 | 211 | #[test] 212 | fn max_active1() { 213 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 214 | assert_eq!(max("a".to_owned(), "b".to_owned()), "a".to_owned()); 215 | assert_eq!(max("b".to_owned(), "a".to_owned()), "a".to_owned()); 216 | }) 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /mutagen-selftest/src/mutator/test_binop_eq.rs: -------------------------------------------------------------------------------- 1 | mod test_eq { 2 | 3 | use ::mutagen::mutate; 4 | use ::mutagen::MutagenRuntimeConfig; 5 | 6 | // simple comparison 7 | #[mutate(conf = local(expected_mutations = 1), mutators = only(binop_eq))] 8 | fn eq(left: i32, right: i32) -> bool { 9 | left == right 10 | } 11 | #[test] 12 | fn eq_inactive() { 13 | MutagenRuntimeConfig::test_without_mutation(|| { 14 | assert_eq!(eq(1, 2), false); 15 | assert_eq!(eq(3, 3), true); 16 | }) 17 | } 18 | #[test] 19 | fn eq_active1() { 20 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 21 | assert_eq!(eq(1, 2), true); 22 | assert_eq!(eq(3, 3), false); 23 | }) 24 | } 25 | } 26 | mod test_ne { 27 | 28 | use ::mutagen::mutate; 29 | use ::mutagen::MutagenRuntimeConfig; 30 | 31 | // simple comparison 32 | #[mutate(conf = local(expected_mutations = 1), mutators = only(binop_eq))] 33 | fn ne(left: i32, right: i32) -> bool { 34 | left != right 35 | } 36 | #[test] 37 | fn ne_inactive() { 38 | MutagenRuntimeConfig::test_without_mutation(|| { 39 | assert_eq!(ne(1, 2), true); 40 | assert_eq!(ne(3, 3), false); 41 | }) 42 | } 43 | #[test] 44 | fn ne_active1() { 45 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 46 | assert_eq!(ne(1, 2), false); 47 | assert_eq!(ne(3, 3), true); 48 | }) 49 | } 50 | } 51 | 52 | mod eq_but_not_copy { 53 | 54 | use ::mutagen::mutate; 55 | use ::mutagen::MutagenRuntimeConfig; 56 | 57 | #[derive(PartialEq, Eq)] 58 | struct EqButNotCopy; 59 | 60 | #[mutate(conf = local(expected_mutations = 1), mutators = only(binop_eq))] 61 | fn eq(x: &EqButNotCopy, y: &EqButNotCopy) -> bool { 62 | *x == *y 63 | } 64 | #[test] 65 | fn eq_inactive() { 66 | MutagenRuntimeConfig::test_without_mutation(|| { 67 | assert!(eq(&EqButNotCopy, &EqButNotCopy)); 68 | }) 69 | } 70 | #[test] 71 | fn eq_active1() { 72 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 73 | assert!(!eq(&EqButNotCopy, &EqButNotCopy)); 74 | }) 75 | } 76 | } 77 | 78 | mod divides { 79 | 80 | use ::mutagen::mutate; 81 | use ::mutagen::MutagenRuntimeConfig; 82 | 83 | #[mutate(conf = local(expected_mutations = 1), mutators = only(binop_eq))] 84 | fn divides(x: u32, y: u32) -> bool { 85 | x % y == 0u32 86 | } 87 | 88 | #[test] 89 | fn divides_inactive() { 90 | MutagenRuntimeConfig::test_without_mutation(|| { 91 | assert!(divides(2, 2)); 92 | assert!(!divides(3, 4)); 93 | }) 94 | } 95 | #[test] 96 | fn divides_active() { 97 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 98 | assert!(!divides(2, 2)); 99 | assert!(divides(3, 4)); 100 | }) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /mutagen-selftest/src/mutator/test_binop_num.rs: -------------------------------------------------------------------------------- 1 | mod test_sum_i32 { 2 | 3 | use ::mutagen::mutate; 4 | use ::mutagen::MutagenRuntimeConfig; 5 | 6 | // simple function that sums two values 7 | #[mutate(conf = local(expected_mutations = 1), mutators = only(binop_num))] 8 | fn sum_i32() -> i32 { 9 | 5 + 1 10 | } 11 | #[test] 12 | fn sum_u32_inactive() { 13 | MutagenRuntimeConfig::test_without_mutation(|| { 14 | assert_eq!(sum_i32(), 6); 15 | }) 16 | } 17 | #[test] 18 | fn sum_u32_active1() { 19 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 20 | assert_eq!(sum_i32(), 4); 21 | }) 22 | } 23 | } 24 | 25 | mod test_sum_u32 { 26 | 27 | use ::mutagen::mutate; 28 | use ::mutagen::MutagenRuntimeConfig; 29 | 30 | // simple function that sums 2 u32 values 31 | #[mutate(conf = local(expected_mutations = 1), mutators = only(binop_num))] 32 | fn sum_u32() -> u32 { 33 | 5 + 1 34 | } 35 | #[test] 36 | fn sum_u32_inactive() { 37 | MutagenRuntimeConfig::test_without_mutation(|| { 38 | assert_eq!(sum_u32(), 6); 39 | }) 40 | } 41 | #[test] 42 | fn sum_u32_active1() { 43 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 44 | assert_eq!(sum_u32(), 4); 45 | }) 46 | } 47 | } 48 | 49 | mod test_str_add { 50 | 51 | use ::mutagen::mutate; 52 | use ::mutagen::MutagenRuntimeConfig; 53 | 54 | // strings cannot be subtracted, the mutation that changes `+` into `-` should panic 55 | #[mutate(conf = local(expected_mutations = 1), mutators = only(binop_num))] 56 | fn str_add() -> String { 57 | "a".to_string() + "b" 58 | } 59 | #[test] 60 | fn str_add_inactive() { 61 | MutagenRuntimeConfig::test_without_mutation(|| { 62 | assert_eq!(&str_add(), "ab"); 63 | }) 64 | } 65 | #[test] 66 | #[should_panic] 67 | fn str_add_active1() { 68 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 69 | str_add(); 70 | }) 71 | } 72 | } 73 | 74 | mod test_multiple_adds { 75 | 76 | use ::mutagen::mutate; 77 | use ::mutagen::MutagenRuntimeConfig; 78 | 79 | // sum of multiple values without brackets 80 | #[mutate(conf = local(expected_mutations = 2), mutators = only(binop_num))] 81 | pub fn multiple_adds() -> usize { 82 | 5 + 4 + 1 83 | } 84 | 85 | #[test] 86 | fn multiple_adds_inactive() { 87 | MutagenRuntimeConfig::test_without_mutation(|| { 88 | assert_eq!(multiple_adds(), 10); 89 | }) 90 | } 91 | #[test] 92 | fn multiple_adds_active1() { 93 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 94 | assert_eq!(multiple_adds(), 2); 95 | }) 96 | } 97 | #[test] 98 | fn multiple_adds_active2() { 99 | MutagenRuntimeConfig::test_with_mutation_id(2, || { 100 | assert_eq!(multiple_adds(), 8); 101 | }) 102 | } 103 | } 104 | 105 | mod test_sub_f64 { 106 | 107 | use ::mutagen::mutate; 108 | use ::mutagen::MutagenRuntimeConfig; 109 | 110 | // sum of multiple values without brackets 111 | #[mutate(conf = local(expected_mutations = 1), mutators = only(binop_num))] 112 | pub fn sub_f64() -> f64 { 113 | 1.0 - 2.0 114 | } 115 | 116 | #[test] 117 | fn sub_f64_inactive() { 118 | MutagenRuntimeConfig::test_without_mutation(|| { 119 | assert_eq!(sub_f64(), -1.0); 120 | }) 121 | } 122 | #[test] 123 | fn sub_f64_active1() { 124 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 125 | assert_eq!(sub_f64(), 3.0); 126 | }) 127 | } 128 | } 129 | 130 | mod test_mul_2_3 { 131 | 132 | use ::mutagen::mutate; 133 | use ::mutagen::MutagenRuntimeConfig; 134 | 135 | // multiplication of two integers 136 | #[mutate(conf = local(expected_mutations = 1), mutators = only(binop_num))] 137 | pub fn mul_2_3() -> u32 { 138 | 2 * 3 139 | } 140 | 141 | #[test] 142 | fn mul_2_3_inactive() { 143 | MutagenRuntimeConfig::test_without_mutation(|| { 144 | assert_eq!(mul_2_3(), 6); 145 | }) 146 | } 147 | #[test] 148 | fn mul_2_3_active1() { 149 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 150 | assert_eq!(mul_2_3(), 0); 151 | }) 152 | } 153 | } 154 | 155 | mod test_div_4_4 { 156 | 157 | use ::mutagen::mutate; 158 | use ::mutagen::MutagenRuntimeConfig; 159 | 160 | // division of two integers 161 | #[mutate(conf = local(expected_mutations = 1), mutators = only(binop_num))] 162 | pub fn div_4_4() -> u32 { 163 | 4 / 4 164 | } 165 | 166 | #[test] 167 | fn div_4_4_inactive() { 168 | MutagenRuntimeConfig::test_without_mutation(|| { 169 | assert_eq!(div_4_4(), 1); 170 | }) 171 | } 172 | #[test] 173 | fn div_4_4_active1() { 174 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 175 | assert_eq!(div_4_4(), 16); 176 | }) 177 | } 178 | } 179 | 180 | mod test_add_after_if { 181 | use ::mutagen::mutate; 182 | use ::mutagen::MutagenRuntimeConfig; 183 | 184 | // addition after an if expression 185 | #[mutate(conf = local(expected_mutations = 1), mutators = only(binop_num))] 186 | pub fn add_after_if(bit: bool) -> i8 { 187 | (if bit { 1 } else { 0 }) + 2 188 | } 189 | 190 | #[test] 191 | fn add_after_if_inactive() { 192 | MutagenRuntimeConfig::test_without_mutation(|| { 193 | assert_eq!(add_after_if(true), 3); 194 | assert_eq!(add_after_if(false), 2); 195 | }) 196 | } 197 | #[test] 198 | fn add_after_if_active1() { 199 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 200 | assert_eq!(add_after_if(true), -1); 201 | assert_eq!(add_after_if(false), -2); 202 | }) 203 | } 204 | } 205 | 206 | mod test_add_after_block { 207 | use ::mutagen::mutate; 208 | use ::mutagen::MutagenRuntimeConfig; 209 | 210 | // addition after a block expression 211 | #[mutate(conf = local(expected_mutations = 1), mutators = only(binop_num))] 212 | pub fn add_after_block() -> i8 { 213 | ({ 214 | ""; 215 | 1 216 | }) + 2 217 | } 218 | 219 | #[test] 220 | fn add_after_block_inactive() { 221 | MutagenRuntimeConfig::test_without_mutation(|| { 222 | assert_eq!(add_after_block(), 3); 223 | }) 224 | } 225 | #[test] 226 | fn add_after_block_active1() { 227 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 228 | assert_eq!(add_after_block(), -1); 229 | }) 230 | } 231 | } 232 | 233 | mod test_mul_i64_with_tempvar { 234 | 235 | use ::mutagen::mutate; 236 | use ::mutagen::MutagenRuntimeConfig; 237 | 238 | // multiplies two numbers, the first one is a temporary variable 239 | #[mutate(conf = local(expected_mutations = 1), mutators = only(binop_num))] 240 | pub fn mul_with_tempvar() -> i64 { 241 | let x = 4; 242 | x * 2 243 | } 244 | 245 | #[test] 246 | fn mul_with_tempvar_inactive() { 247 | MutagenRuntimeConfig::test_without_mutation(|| { 248 | assert_eq!(mul_with_tempvar(), 8); 249 | }) 250 | } 251 | #[test] 252 | fn mul_with_tempvar_active1() { 253 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 254 | assert_eq!(mul_with_tempvar(), 2); 255 | }) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /mutagen-selftest/src/mutator/test_lit_bool.rs: -------------------------------------------------------------------------------- 1 | mod test_simple_true { 2 | 3 | use ::mutagen::mutate; 4 | use ::mutagen::MutagenRuntimeConfig; 5 | 6 | #[mutate(conf = local(expected_mutations = 1), mutators = only(lit_bool))] 7 | fn simple_true() -> bool { 8 | true 9 | } 10 | #[test] 11 | fn simple_true_inactive() { 12 | MutagenRuntimeConfig::test_without_mutation(|| { 13 | assert_eq!(simple_true(), true); 14 | }) 15 | } 16 | #[test] 17 | fn simple_true_active() { 18 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 19 | assert_eq!(simple_true(), false); 20 | }) 21 | } 22 | } 23 | 24 | mod test_simple_false { 25 | 26 | use ::mutagen::mutate; 27 | use ::mutagen::MutagenRuntimeConfig; 28 | 29 | // constant false 30 | #[mutate(conf = local(expected_mutations = 1), mutators = only(lit_bool))] 31 | fn simple_false() -> bool { 32 | false 33 | } 34 | #[test] 35 | fn simple_false_inactive() { 36 | MutagenRuntimeConfig::test_without_mutation(|| { 37 | assert_eq!(simple_false(), false); 38 | }) 39 | } 40 | #[test] 41 | fn simple_false_active() { 42 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 43 | assert_eq!(simple_false(), true); 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /mutagen-selftest/src/mutator/test_lit_int.rs: -------------------------------------------------------------------------------- 1 | mod test_sum_u32 { 2 | 3 | use ::mutagen::mutate; 4 | use ::mutagen::MutagenRuntimeConfig; 5 | 6 | // test that literals, that are nested in a outside expressen, are mutated 7 | #[mutate(conf = local(expected_mutations = 4), mutators = only(lit_int))] 8 | fn sum_u32() -> u32 { 9 | 1 + 2 10 | } 11 | #[test] 12 | fn sum_u32_inactive() { 13 | MutagenRuntimeConfig::test_without_mutation(|| { 14 | assert_eq!(sum_u32(), 3); 15 | }) 16 | } 17 | // first literal -1 18 | #[test] 19 | fn sum_u32_active1() { 20 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 21 | assert_eq!(sum_u32(), 4); 22 | }) 23 | } 24 | 25 | // second literal +1 26 | #[test] 27 | fn sum_u32_active2() { 28 | MutagenRuntimeConfig::test_with_mutation_id(2, || { 29 | assert_eq!(sum_u32(), 2); 30 | }) 31 | } 32 | // second literal -1 33 | #[test] 34 | fn sum_u32_active3() { 35 | MutagenRuntimeConfig::test_with_mutation_id(3, || { 36 | assert_eq!(sum_u32(), 4); 37 | }) 38 | } 39 | // first literal -1 40 | #[test] 41 | fn sum_u32_active4() { 42 | MutagenRuntimeConfig::test_with_mutation_id(4, || { 43 | assert_eq!(sum_u32(), 2); 44 | }) 45 | } 46 | } 47 | 48 | mod test_lit_u8_suffixed { 49 | 50 | use ::mutagen::mutate; 51 | use ::mutagen::MutagenRuntimeConfig; 52 | 53 | #[mutate(conf = local(expected_mutations = 2), mutators = only(lit_int))] 54 | fn lit_u8_suffixed() -> u8 { 55 | 1u8 56 | } 57 | #[test] 58 | fn lit_u8_suffixed_inactive() { 59 | MutagenRuntimeConfig::test_without_mutation(|| { 60 | assert_eq!(lit_u8_suffixed(), 1); 61 | }) 62 | } 63 | // literal +1 64 | #[test] 65 | fn lit_u8_suffixed_active1() { 66 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 67 | assert_eq!(lit_u8_suffixed(), 2); 68 | }) 69 | } 70 | // literal -1 71 | #[test] 72 | fn lit_u8_suffixed_active2() { 73 | MutagenRuntimeConfig::test_with_mutation_id(2, || { 74 | assert_eq!(lit_u8_suffixed(), 0); 75 | }) 76 | } 77 | } 78 | mod test_lit_u8_overflown_literal { 79 | 80 | use ::mutagen::mutate; 81 | use ::mutagen::MutagenRuntimeConfig; 82 | 83 | #[mutate(conf = local(expected_mutations = 2), mutators = only(lit_int))] 84 | fn lit_u8_overflown_literal() -> u8 { 85 | 255 86 | } 87 | #[test] 88 | fn lit_u8_overflown_literal_inactive() { 89 | MutagenRuntimeConfig::test_without_mutation(|| { 90 | assert_eq!(lit_u8_overflown_literal(), 255); 91 | }) 92 | } 93 | // literal +1 -> wraps around 94 | #[test] 95 | fn lit_u8_overflown_literal_active1() { 96 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 97 | assert_eq!(lit_u8_overflown_literal(), 0); 98 | }) 99 | } 100 | // literal -1 101 | #[test] 102 | fn lit_u8_overflown_literal_active2() { 103 | MutagenRuntimeConfig::test_with_mutation_id(2, || { 104 | assert_eq!(lit_u8_overflown_literal(), 254); 105 | }) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /mutagen-selftest/src/mutator/test_lit_str.rs: -------------------------------------------------------------------------------- 1 | mod test_return_non_empty_string { 2 | 3 | use ::mutagen::mutate; 4 | use ::mutagen::MutagenRuntimeConfig; 5 | 6 | #[mutate(conf = local(expected_mutations = 3), mutators = only(lit_str))] 7 | fn return_non_empty_string() -> String { 8 | #[allow(unused_parens)] 9 | let s = "a"; 10 | s.to_string() 11 | } 12 | 13 | #[test] 14 | fn inactive() { 15 | MutagenRuntimeConfig::test_without_mutation(|| { 16 | assert_eq!(return_non_empty_string(), "a".to_string()); 17 | }) 18 | } 19 | 20 | #[test] 21 | fn active_clear() { 22 | let _ = MutagenRuntimeConfig::get_default(); 23 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 24 | assert_eq!(return_non_empty_string(), "".to_string()); 25 | }) 26 | } 27 | 28 | #[test] 29 | fn active_prepend() { 30 | MutagenRuntimeConfig::test_with_mutation_id(2, || { 31 | assert_eq!(return_non_empty_string(), "-a".to_string()); 32 | }) 33 | } 34 | 35 | #[test] 36 | fn active_append() { 37 | MutagenRuntimeConfig::test_with_mutation_id(3, || { 38 | assert_eq!(return_non_empty_string(), "a-".to_string()); 39 | }) 40 | } 41 | } 42 | 43 | mod test_return_check_equals_a { 44 | 45 | use ::mutagen::mutate; 46 | use ::mutagen::MutagenRuntimeConfig; 47 | 48 | #[mutate(conf = local(expected_mutations = 3), mutators = only(lit_str))] 49 | fn check_equals_a(input: &str) -> bool { 50 | "a" == input 51 | } 52 | 53 | #[test] 54 | fn inactive() { 55 | MutagenRuntimeConfig::test_without_mutation(|| { 56 | assert_eq!(check_equals_a("a"), true); 57 | }) 58 | } 59 | 60 | #[test] 61 | fn active_clear() { 62 | let _ = MutagenRuntimeConfig::get_default(); 63 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 64 | assert_eq!(check_equals_a("a"), false); 65 | assert_eq!(check_equals_a(""), true); 66 | }) 67 | } 68 | 69 | #[test] 70 | fn active_prepend() { 71 | MutagenRuntimeConfig::test_with_mutation_id(2, || { 72 | assert_eq!(check_equals_a("a"), false); 73 | assert_eq!(check_equals_a("-a"), true); 74 | }) 75 | } 76 | 77 | #[test] 78 | fn active_append() { 79 | MutagenRuntimeConfig::test_with_mutation_id(3, || { 80 | assert_eq!(check_equals_a("a"), false); 81 | assert_eq!(check_equals_a("a-"), true); 82 | }) 83 | } 84 | } 85 | 86 | mod test_return_empty_string { 87 | 88 | use ::mutagen::mutate; 89 | use ::mutagen::MutagenRuntimeConfig; 90 | 91 | #[mutate(conf = local(expected_mutations = 1), mutators = only(lit_str))] 92 | fn return_empty_string() -> String { 93 | #[allow(unused_parens)] 94 | let s = ""; 95 | s.to_string() 96 | } 97 | 98 | #[test] 99 | fn inactive() { 100 | MutagenRuntimeConfig::test_without_mutation(|| { 101 | assert_eq!(return_empty_string(), "".to_string()); 102 | }) 103 | } 104 | 105 | #[test] 106 | fn active_set() { 107 | let _ = MutagenRuntimeConfig::get_default(); 108 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 109 | assert_eq!(return_empty_string(), "A".to_string()); 110 | }) 111 | } 112 | } 113 | 114 | mod test_return_check_equals_empty_str { 115 | 116 | use ::mutagen::mutate; 117 | use ::mutagen::MutagenRuntimeConfig; 118 | 119 | #[mutate(conf = local(expected_mutations = 1), mutators = only(lit_str))] 120 | fn check_equals_empty_str(input: &str) -> bool { 121 | "" == input 122 | } 123 | 124 | #[test] 125 | fn inactive() { 126 | MutagenRuntimeConfig::test_without_mutation(|| { 127 | assert_eq!(check_equals_empty_str(""), true); 128 | }) 129 | } 130 | 131 | #[test] 132 | fn active_set() { 133 | let _ = MutagenRuntimeConfig::get_default(); 134 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 135 | assert_eq!(check_equals_empty_str(""), false); 136 | assert_eq!(check_equals_empty_str("A"), true); 137 | }) 138 | } 139 | } 140 | 141 | mod test_temporary_variable_1 { 142 | use ::mutagen::mutate; 143 | use ::mutagen::MutagenRuntimeConfig; 144 | 145 | #[mutate(conf = local(expected_mutations = 1), mutators = only(lit_str))] 146 | fn a() -> usize { 147 | #[allow(unused_parens)] 148 | let x = ""; 149 | x.len() 150 | } 151 | 152 | #[test] 153 | fn inactive() { 154 | MutagenRuntimeConfig::test_without_mutation(|| { 155 | assert_eq!(a(), 0); 156 | }) 157 | } 158 | 159 | #[test] 160 | fn active_set() { 161 | let _ = MutagenRuntimeConfig::get_default(); 162 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 163 | assert_eq!(a(), 1); 164 | }) 165 | } 166 | } 167 | 168 | mod test_to_string { 169 | use ::mutagen::mutate; 170 | use ::mutagen::MutagenRuntimeConfig; 171 | 172 | #[mutate(conf = local(expected_mutations = 1), mutators = only(lit_str))] 173 | fn a() -> &'static str { 174 | "" 175 | } 176 | 177 | #[test] 178 | fn inactive() { 179 | MutagenRuntimeConfig::test_without_mutation(|| { 180 | assert_eq!(a(), "".to_string()); 181 | }) 182 | } 183 | 184 | #[test] 185 | fn active_set() { 186 | let _ = MutagenRuntimeConfig::get_default(); 187 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 188 | assert_eq!(a(), "A".to_string()); 189 | }) 190 | } 191 | } 192 | 193 | mod test_temporary_variable_2 { 194 | 195 | use ::mutagen::mutate; 196 | use ::mutagen::MutagenRuntimeConfig; 197 | 198 | #[mutate(conf = local(expected_mutations = 1), mutators = only(lit_str))] 199 | fn a() -> &'static str { 200 | "" 201 | } 202 | 203 | #[test] 204 | fn inactive() { 205 | MutagenRuntimeConfig::test_without_mutation(|| { 206 | assert_eq!(a(), ""); 207 | }) 208 | } 209 | 210 | #[test] 211 | fn active_clear() { 212 | let _ = MutagenRuntimeConfig::get_default(); 213 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 214 | assert_eq!(a(), "A"); 215 | }) 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /mutagen-selftest/src/mutator/test_stmt_call.rs: -------------------------------------------------------------------------------- 1 | mod test_vecpush { 2 | 3 | use ::mutagen::mutate; 4 | use ::mutagen::MutagenRuntimeConfig; 5 | 6 | /// create a vector and push a single value to it. 7 | #[mutate(conf = local(expected_mutations = 1), mutators = only(stmt_call))] 8 | fn vecpush() -> Vec { 9 | let mut x = Vec::new(); 10 | x.push(1); 11 | x 12 | } 13 | #[test] 14 | fn vecpush_inactive() { 15 | MutagenRuntimeConfig::test_without_mutation(|| assert_eq!(vecpush(), vec![1])) 16 | } 17 | #[test] 18 | fn vecpush_active() { 19 | MutagenRuntimeConfig::test_with_mutation_id(1, || assert_eq!(vecpush(), Vec::::new())) 20 | } 21 | } 22 | 23 | mod test_set_to_1 { 24 | 25 | use ::mutagen::mutate; 26 | use ::mutagen::MutagenRuntimeConfig; 27 | 28 | /// sets the given reference to 1 29 | fn set_to_1_fn(x: &mut i32) { 30 | *x = 1; 31 | } 32 | 33 | /// returns `1`, by calling the function `set_to_one` 34 | #[mutate(conf = local(expected_mutations = 1), mutators = only(stmt_call))] 35 | fn set_to_1() -> i32 { 36 | let mut x = 0; 37 | set_to_1_fn(&mut x); 38 | x 39 | } 40 | #[test] 41 | fn set_to_1_inactive() { 42 | MutagenRuntimeConfig::test_without_mutation(|| assert_eq!(set_to_1(), 1)) 43 | } 44 | #[test] 45 | fn set_to_1_active() { 46 | MutagenRuntimeConfig::test_with_mutation_id(1, || assert_eq!(set_to_1(), 0)) 47 | } 48 | } 49 | 50 | #[allow(unreachable_code)] 51 | mod test_early_return { 52 | 53 | use ::mutagen::mutate; 54 | use ::mutagen::MutagenRuntimeConfig; 55 | 56 | /// returns `1`, by returning from the argument of a function call 57 | #[mutate(conf = local(expected_mutations = 1), mutators = only(stmt_call))] 58 | fn return_early() -> i32 { 59 | [1u8].get::(return 1); 60 | } 61 | #[test] 62 | fn return_early_inactive() { 63 | MutagenRuntimeConfig::test_without_mutation(|| assert_eq!(return_early(), 1)) 64 | } 65 | #[test] 66 | #[should_panic] 67 | fn return_early_active() { 68 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 69 | return_early(); 70 | }) 71 | } 72 | } 73 | 74 | #[allow(unreachable_code)] 75 | mod test_stmt_never { 76 | 77 | use ::mutagen::mutate; 78 | use ::mutagen::MutagenRuntimeConfig; 79 | 80 | /// returns `!`, by panicing 81 | #[mutate(conf = local(expected_mutations = 1), mutators = only(stmt_call))] 82 | fn stmt_never() -> ! { 83 | [1u8].get::(panic!()); 84 | } 85 | #[test] 86 | #[should_panic] 87 | fn stmt_never_inactive() { 88 | MutagenRuntimeConfig::test_without_mutation(|| stmt_never()) 89 | } 90 | #[test] 91 | #[should_panic] 92 | fn stmt_never_active() { 93 | MutagenRuntimeConfig::test_with_mutation_id(1, || stmt_never()) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /mutagen-selftest/src/mutator/test_unop_not.rs: -------------------------------------------------------------------------------- 1 | mod test_boolnot { 2 | 3 | use ::mutagen::mutate; 4 | use ::mutagen::MutagenRuntimeConfig; 5 | 6 | // simple function that negates the input 7 | #[mutate(conf = local(expected_mutations = 1), mutators = only(unop_not))] 8 | fn boolnot(x: bool) -> bool { 9 | !x 10 | } 11 | #[test] 12 | fn boolnot_inactive() { 13 | MutagenRuntimeConfig::test_without_mutation(|| { 14 | assert_eq!(boolnot(false), true); 15 | assert_eq!(boolnot(true), false); 16 | }) 17 | } 18 | #[test] 19 | fn boolnot_active() { 20 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 21 | assert_eq!(boolnot(false), false); 22 | assert_eq!(boolnot(true), true); 23 | }) 24 | } 25 | } 26 | 27 | mod test_optimistic_incorrect { 28 | 29 | use ::mutagen::mutate; 30 | use ::mutagen::mutator::mutator_unop_not::optimistic_types::{ 31 | TypeWithNotOtherOutput, TypeWithNotTarget, 32 | }; 33 | use ::mutagen::MutagenRuntimeConfig; 34 | 35 | // strings cannot be subtracted, the mutation that changes `+` into `-` should panic 36 | #[mutate(conf = local(expected_mutations = 1), mutators = only(unop_not))] 37 | fn optimistic_incorrect(x: TypeWithNotOtherOutput) -> TypeWithNotTarget { 38 | !x 39 | } 40 | #[test] 41 | fn optimistic_incorrect_inactive() { 42 | MutagenRuntimeConfig::test_without_mutation(|| { 43 | assert_eq!( 44 | optimistic_incorrect(TypeWithNotOtherOutput()), 45 | TypeWithNotTarget() 46 | ); 47 | }) 48 | } 49 | #[test] 50 | #[should_panic] 51 | fn optimistic_incorrect_active1() { 52 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 53 | optimistic_incorrect(TypeWithNotOtherOutput()); 54 | }) 55 | } 56 | } 57 | 58 | mod test_double_negation { 59 | use ::mutagen::mutate; 60 | use ::mutagen::MutagenRuntimeConfig; 61 | 62 | // double negation 63 | #[mutate(conf = local(expected_mutations = 2), mutators = only(unop_not))] 64 | fn double_negation(x: bool) -> bool { 65 | !!x 66 | } 67 | #[test] 68 | fn double_negation_inactive() { 69 | MutagenRuntimeConfig::test_without_mutation(|| { 70 | assert_eq!(double_negation(true), true); 71 | assert_eq!(double_negation(false), false); 72 | }) 73 | } 74 | #[test] 75 | fn double_negation_active1() { 76 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 77 | assert_eq!(double_negation(true), false); 78 | assert_eq!(double_negation(false), true); 79 | }) 80 | } 81 | #[test] 82 | fn double_negation_active2() { 83 | MutagenRuntimeConfig::test_with_mutation_id(2, || { 84 | assert_eq!(double_negation(true), false); 85 | assert_eq!(double_negation(false), true); 86 | }) 87 | } 88 | } 89 | 90 | mod test_bit_not_u32 { 91 | use ::mutagen::mutate; 92 | use ::mutagen::MutagenRuntimeConfig; 93 | 94 | // double negation 95 | #[mutate(conf = local(expected_mutations = 1), mutators = only(unop_not))] 96 | fn bit_not_u32() -> u32 { 97 | !0 98 | } 99 | #[test] 100 | fn double_negation_inactive() { 101 | MutagenRuntimeConfig::test_without_mutation(|| { 102 | assert_eq!(bit_not_u32(), !0); 103 | }) 104 | } 105 | #[test] 106 | fn double_negation_active1() { 107 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 108 | assert_eq!(bit_not_u32(), 0); 109 | }) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /mutagen-selftest/src/runtime_config.rs: -------------------------------------------------------------------------------- 1 | use ::mutagen::MutagenRuntimeConfig; 2 | 3 | #[test] 4 | fn with_mutation_id_1() { 5 | MutagenRuntimeConfig::test_with_mutation_id(1, || { 6 | assert_eq!(MutagenRuntimeConfig::get_default().mutation_id(), Some(1)); 7 | }) 8 | } 9 | #[test] 10 | fn without_mutation() { 11 | MutagenRuntimeConfig::test_without_mutation(|| { 12 | assert_eq!(MutagenRuntimeConfig::get_default().mutation_id(), None); 13 | }) 14 | } 15 | #[test] 16 | #[should_panic] 17 | fn with_mutation_id_0() { 18 | MutagenRuntimeConfig::with_mutation_id(0); 19 | } 20 | -------------------------------------------------------------------------------- /mutagen-selftest/src/test_not_mutated.rs: -------------------------------------------------------------------------------- 1 | //! tests that ensure that no mutation is triggered in certain cases 2 | 3 | mod static_const { 4 | 5 | use ::mutagen::mutate; 6 | 7 | #[mutate(conf = local(expected_mutations = 0))] 8 | const X: i32 = 5; 9 | 10 | #[test] 11 | fn x_is_5() { 12 | assert_eq!(X, 5) 13 | } 14 | } 15 | mod const_fn { 16 | 17 | use ::mutagen::mutate; 18 | 19 | #[mutate(conf = local(expected_mutations = 0))] 20 | const fn x() -> i32 { 21 | 5 22 | } 23 | 24 | #[test] 25 | fn x_is_5() { 26 | assert_eq!(x(), 5) 27 | } 28 | } 29 | 30 | mod const_method { 31 | 32 | use ::mutagen::mutate; 33 | 34 | struct X; 35 | 36 | #[mutate(conf = local(expected_mutations = 0))] 37 | impl X { 38 | const fn x() -> i32 { 39 | 5 40 | } 41 | } 42 | 43 | #[test] 44 | fn x_is_5() { 45 | assert_eq!(X::x(), 5) 46 | } 47 | } 48 | 49 | mod array_expr_size { 50 | 51 | use ::mutagen::mutate; 52 | 53 | #[mutate(conf = local(expected_mutations = 0))] 54 | fn x() -> Vec<()> { 55 | [(); 5].to_vec() 56 | } 57 | 58 | #[test] 59 | fn x_is_vec5() { 60 | assert_eq!(x().len(), 5) 61 | } 62 | } 63 | 64 | mod array_returntype_size { 65 | 66 | use ::mutagen::mutate; 67 | 68 | #[mutate(conf = local(expected_mutations = 0))] 69 | fn x() -> Option<[(); 5]> { 70 | None 71 | } 72 | 73 | #[test] 74 | fn x_is_none() { 75 | assert_eq!(x(), None) 76 | } 77 | } 78 | 79 | mod tuple_index_access { 80 | 81 | use ::mutagen::mutate; 82 | 83 | #[mutate(conf = local(expected_mutations = 0), mutators = not(lit_str))] 84 | fn x() -> &'static str { 85 | ((), "").1 86 | } 87 | 88 | #[test] 89 | fn x_is_emptystr() { 90 | assert_eq!(x(), "") 91 | } 92 | } 93 | mod int_as_pattern { 94 | 95 | use ::mutagen::mutate; 96 | 97 | #[mutate(conf = local(expected_mutations = 0), mutators = not(lit_str))] 98 | fn x(i: i8) -> &'static str { 99 | match i { 100 | 0 => "zero", 101 | 1..=127 => "positive", 102 | _ => "negative", 103 | } 104 | } 105 | 106 | #[test] 107 | fn x_zero() { 108 | assert_eq!(x(0), "zero") 109 | } 110 | #[test] 111 | fn x_one_positive() { 112 | assert_eq!(x(1), "positive") 113 | } 114 | #[test] 115 | fn x_minus_one_negative() { 116 | assert_eq!(x(-1), "negative") 117 | } 118 | } 119 | 120 | mod unsafe_fn { 121 | use ::mutagen::mutate; 122 | 123 | #[mutate(conf = local(expected_mutations = 0))] 124 | unsafe fn x() -> u8 { 125 | 5 126 | } 127 | 128 | #[test] 129 | fn x_is_5() { 130 | unsafe { assert_eq!(x(), 5) } 131 | } 132 | } 133 | 134 | mod unsafe_method { 135 | 136 | use ::mutagen::mutate; 137 | 138 | struct X; 139 | 140 | #[mutate(conf = local(expected_mutations = 0))] 141 | impl X { 142 | unsafe fn x() -> i32 { 143 | 5 144 | } 145 | } 146 | 147 | #[test] 148 | fn x_is_5() { 149 | assert_eq!(unsafe { X::x() }, 5) 150 | } 151 | } 152 | 153 | mod unsafe_block { 154 | 155 | use ::mutagen::mutate; 156 | 157 | #[mutate(conf = local(expected_mutations = 0))] 158 | fn x() -> u8 { 159 | // this is a dummy-unsafe-block with something that *could* be mutated but should not 160 | #[allow(unused_unsafe)] 161 | unsafe { 162 | 5 163 | } 164 | } 165 | 166 | #[test] 167 | fn x_is_5() { 168 | assert_eq!(x(), 5) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /mutagen-transform/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mutagen-transform" 3 | version = "0.2.0" 4 | authors = ["Andre Bogus ", "power-fungus "] 5 | edition = "2018" 6 | license = "Apache-2.0/MIT" 7 | 8 | [dependencies] 9 | mutagen-core = { path = "../mutagen-core" } 10 | proc-macro2 = "1.0.29" 11 | 12 | [lib] 13 | proc_macro = true 14 | doctest = false 15 | test = false 16 | -------------------------------------------------------------------------------- /mutagen-transform/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | use mutagen_core::do_transform_item; 4 | 5 | #[proc_macro_attribute] 6 | pub fn mutate( 7 | attr: proc_macro::TokenStream, 8 | input: proc_macro::TokenStream, 9 | ) -> proc_macro::TokenStream { 10 | do_transform_item(attr.into(), input.into()).into() 11 | } 12 | -------------------------------------------------------------------------------- /mutagen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mutagen" 3 | version = "0.2.0" 4 | authors = ["Andre Bogus ", "Samuel Pilz "] 5 | edition = "2018" 6 | license = "Apache-2.0/MIT" 7 | 8 | [dependencies] 9 | mutagen-transform = { path = "../mutagen-transform" } 10 | mutagen-core = { path = "../mutagen-core" } 11 | 12 | [lib] 13 | doctest = false 14 | test = false 15 | -------------------------------------------------------------------------------- /mutagen/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use mutagen_core::mutator; 2 | pub use mutagen_core::MutagenRuntimeConfig; 3 | pub use mutagen_transform::mutate; 4 | --------------------------------------------------------------------------------