added draft for rust_embedded_unit_testing
This commit is contained in:
		
							parent
							
								
									c2ffa5a577
								
							
						
					
					
						commit
						48d92f7638
					
				
					 2 changed files with 122 additions and 0 deletions
				
			
		
							
								
								
									
										115
									
								
								content/posts/rustEmbeddedUnitTesting.adoc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								content/posts/rustEmbeddedUnitTesting.adoc
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,115 @@
 | 
				
			||||||
 | 
					+++
 | 
				
			||||||
 | 
					title = "Rust Embedded Unit Testing"
 | 
				
			||||||
 | 
					date = 2023-10-28T18:41:37-05:00
 | 
				
			||||||
 | 
					draft = false
 | 
				
			||||||
 | 
					+++
 | 
				
			||||||
 | 
					:caution-caption: pass:[<span style="font-size: 2em">☠</span>]
 | 
				
			||||||
 | 
					:important-caption: pass:[<span style="font-size: 2em">❗</span>]
 | 
				
			||||||
 | 
					:note-caption: pass:[<span style="font-size: 2em">✏️</span>]
 | 
				
			||||||
 | 
					:tip-caption: pass:[<span style="font-size: 2em">💡</span>]
 | 
				
			||||||
 | 
					:warning-caption: pass:[<span style="font-size: 2em">⚠</span>]
 | 
				
			||||||
 | 
					:toc:
 | 
				
			||||||
 | 
					:toclevels: 6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Ive been messing around with embedded rust recently, using the BBC micro:bit as a learning platform.
 | 
				
			||||||
 | 
					Its really cool to see a high level language achieving the same results as low level c.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					However, one of my favorite features of rust, the ease of unit testing, is a bit less straightforward to do in cross-compiled, no-std projects.
 | 
				
			||||||
 | 
					Obviously we cant run tests on our local machine that rely on hardware only found on the target board,
 | 
				
			||||||
 | 
					but most of a project is going to be logic independent of the hardware its running on.
 | 
				
			||||||
 | 
					What we really want to do is be able to unit test those independent blocks of logic on our local dev machine.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					== The Root Problem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The root of the problem is that our entire project is setup to depend on our target architecture and its hardware features.
 | 
				
			||||||
 | 
					(you are using cargo embed, right?)
 | 
				
			||||||
 | 
					As long as there is no compiler separation between our logic and our hardware interaction, we cant compile only one for our local machine while leaving out the rest.
 | 
				
			||||||
 | 
					Fortunately, that realization leads us directly to...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					== The Solution
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In Rust, the minimum unit of compilation is the crate.
 | 
				
			||||||
 | 
					This means that if we want to separate our logic from our hardware interaction, we have to put them in separate crates.
 | 
				
			||||||
 | 
					Thankfully, Rust has a feature called https://doc.rust-lang.org/cargo/reference/workspaces.html[workspaces] dedicated to managing several crates in a single project/repo.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					So, what we will do is make a https://doc.rust-lang.org/cargo/reference/workspaces.html[virtual workspace] at the top level of our repo,
 | 
				
			||||||
 | 
					containing 2 or more crates: one (or more, if it makes sense) for our main hardware layer that is set up for cross compilation and flashing using cargo-embed,
 | 
				
			||||||
 | 
					one `#![no-std]` crate containing only hardware independent logic that can be used on any architecture, and do not have cross-compilation explicitly setup.
 | 
				
			||||||
 | 
					The latter set of crate(s) are the ones that we will be able to put unit tests in.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					== Implementation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Our `main_hardware` crate will depend on the `independent_logic` crate, and our `independent_logic` crate cannot depend on any crate that is hardware specific, such as any HALs or BSPs in use.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Lets say that we have a project that looks something like this:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{<highlight console "linenos=false">}}
 | 
				
			||||||
 | 
					.
 | 
				
			||||||
 | 
					├── .cargo
 | 
				
			||||||
 | 
					│   └── config
 | 
				
			||||||
 | 
					├── .gitignore
 | 
				
			||||||
 | 
					├── Cargo.toml
 | 
				
			||||||
 | 
					├── Embed.toml
 | 
				
			||||||
 | 
					├── LICENCE
 | 
				
			||||||
 | 
					├── README.md
 | 
				
			||||||
 | 
					├── build.rs
 | 
				
			||||||
 | 
					├── memory.x
 | 
				
			||||||
 | 
					└── src
 | 
				
			||||||
 | 
					    ├── calibration.rs
 | 
				
			||||||
 | 
					    ├── heading_drawing.rs
 | 
				
			||||||
 | 
					    ├── led.rs
 | 
				
			||||||
 | 
					    ├── line_drawing.rs
 | 
				
			||||||
 | 
					    ├── main.rs
 | 
				
			||||||
 | 
					    └── tilt_compensation.rs
 | 
				
			||||||
 | 
					{{</highlight>}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					=== Separating the crates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`calibration.rs` and `main.rs` are the only modules that depend on hardware features, so they will be the modules going into `hardware_main`.
 | 
				
			||||||
 | 
					The rest will be going into `independent_logic`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					First we create the two new crates, with `hardware_main` being a binary crate and `independent_logic` being a library one, and we move the files to their respective crate.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Then, we move the existing `Cargo.toml`, `Embed.toml`, `build.rs`, `memory.x`, and `.cargo/config` into the `hardware_main`.
 | 
				
			||||||
 | 
					Then, we create a new `Cargo.toml` at the top level, with the only section being a `[workspace]` section,
 | 
				
			||||||
 | 
					including both of the crates in the workspace.
 | 
				
			||||||
 | 
					We edit the `Cargo.toml` of `hardware_main` to point to `independent_logic` as a https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-path-dependencies[path dependency], and remove any dependencies that are no longer used.
 | 
				
			||||||
 | 
					We edit the `Cargo.toml` of `independent_logic` to add any packages that our code depends on.
 | 
				
			||||||
 | 
					We edit `lib.rs` to declare all the modules in `independent_logic` (and declare the crate as `#![no_std]`),
 | 
				
			||||||
 | 
					and remove those declarations from `main.rs`.
 | 
				
			||||||
 | 
					Finally, in the `hardware_main` crate, we start running `cargo check` and fix all the imports that went from being `crate::` imports to `independent_logic::` imports.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In the end, our file tree should look something like this:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{<highlight console "linenos=false">}}
 | 
				
			||||||
 | 
					.
 | 
				
			||||||
 | 
					├── .gitignore
 | 
				
			||||||
 | 
					├── Cargo.toml
 | 
				
			||||||
 | 
					├── LICENCE
 | 
				
			||||||
 | 
					├── README.md
 | 
				
			||||||
 | 
					├── hardware_main
 | 
				
			||||||
 | 
					│   ├── .cargo
 | 
				
			||||||
 | 
					│   │   └── config
 | 
				
			||||||
 | 
					│   ├── Cargo.toml
 | 
				
			||||||
 | 
					│   ├── Embed.toml
 | 
				
			||||||
 | 
					│   ├── build.rs
 | 
				
			||||||
 | 
					│   ├── memory.x
 | 
				
			||||||
 | 
					│   └── src
 | 
				
			||||||
 | 
					│       ├── calibration.rs
 | 
				
			||||||
 | 
					│       ├── led.rs
 | 
				
			||||||
 | 
					│       └── main.rs
 | 
				
			||||||
 | 
					└── independent_logic
 | 
				
			||||||
 | 
					    ├── Cargo.toml
 | 
				
			||||||
 | 
					    └── src
 | 
				
			||||||
 | 
					        ├── heading_drawing.rs
 | 
				
			||||||
 | 
					        ├── lib.rs
 | 
				
			||||||
 | 
					        ├── line_drawing.rs
 | 
				
			||||||
 | 
					        └── tilt_compensation.rs
 | 
				
			||||||
 | 
					{{</highlight>}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					=== Adding tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Now that we have separated the crates from each other, we are free to add unit tests to `independent_logic` the same way we would for any 'normal' rust project.
 | 
				
			||||||
 | 
					However, we must keep in mind that we will only be able to run `cargo test` from inside the `independent_logic` crate.
 | 
				
			||||||
 | 
					Likewise, we will not be able to run `cargo build/check/embed` from the top level workspace, but must run it from the `hardware_main` crate, as that crate is bound to a specific target.
 | 
				
			||||||
 | 
					Cargo commands will generally also not work in the root workspace, as it will try to compile `hardware_main` for our local architecture, inevitably failing.
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,13 @@ showToc = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
== Work Experience
 | 
					== Work Experience
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					=== John Deere
 | 
				
			||||||
 | 
					Embedded Linux Engineer::
 | 
				
			||||||
 | 
					August 2023--current
 | 
				
			||||||
 | 
					* Develop Linux distributions for embedded systems with Yocto
 | 
				
			||||||
 | 
					* Develop Linux hardware drivers for embedded system
 | 
				
			||||||
 | 
					* Use Git to collaborate across teams
 | 
				
			||||||
 | 
					
 | 
				
			||||||
=== Appareo
 | 
					=== Appareo
 | 
				
			||||||
Embedded Systems Intern::
 | 
					Embedded Systems Intern::
 | 
				
			||||||
Summer 2023
 | 
					Summer 2023
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue