/csv_reader

testing CoPilot

Primary LanguageRust

testing CoPilot

I used this little amusing assessment project to test Github's copilot AI. Even though the project was initially a small POC not intended to be re-used, using an AI assistant made me able to literally create code as I thought. This means I did not spend 2 or 3 hours on it (more likely 2 days) but I wanted to see if I could create a little serious open-source grade level coding standard in terms of documentation and tests. I must say I liked every minute I spent on that project.

Architectural choices

The exercise to read a CSV file, process it and dump a CSV as result was somewhat simple but I really wanted to make the project:

  1. reliable
  2. maintainable
  3. observable
  4. scalable

The two first points were addressed by an extensive unit testing coverage that implied every structure and mechanism had to be testable. SOLID is a good way to secure these topics. The project has been made observable through the use of env_logger crate that outputs logs on StdErr so it does not conflict with the business output of the project.

RUST_LOG=info cargo run -- my_file.csv

Performances are achieved by using different threads for reading and processing the data. The maintainable aspect would ensure it is possible to replace or add more processing units. The input file is read by a Reader actor that uses a buffering file reader. It de-serializes and cleans the data and then sends it through a message channel to the Accountant actor. The Accountant keeps the state of the accounts and the transaction disputes. It is heavily unit tested.

The actual implementation uses a set of in-memory collections to store the accountant state through an adapter pattern that enforces the data consistency. This opens the way to either other implementations or easily connect this program to specialized databases when memory space would become a problem. The correctness little “details” (positive amounts, transaction kinds etc.) have been left to Rust type system (and Serde).

For a first version it has been chosen not to use Tokio because not that much IO was involved (maybe I am wrong). Still, it should be easy to turn this code into asynchronous.

The rust_decimal crate has been used to ensure amounts correctness and rounding operations.

Testing and documentation

Tests are a mix of documentation tests and unit tests, most of them have been generated by Copilot. The possibility to use the documentation as test is something that I did not often have the time to implement in my previous projects. Using actors with a bus message is a great way to make controllers testable since it is easy to launch them in a separate thread and feed them from the testing methods.

The Accountant actor uses a shared service to persist its state that can be passed through threads. This service is also used by the Exporter actor to extract and dump the accounts. This pattern makes the controllers easy to test while it ensures consistency through a RwLock.

I used the just tool to launch tests so I could get test doctest and clippy running in one command.

The #![warn(missing_docs)] tag has been added on top of the lib.rs to enforce the documentation of every public structures and attributes.

Errors handling

The errors are handled though two crates: anyhow to wrap errors and give them context and thiserror to easily create custom error types. All errors are logged and, unless something very wrong happen that would compromise data integrity, the program does not panic.

There are no unsafe use in the program.