-
-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(testing): add basic section-based test declaration support #65
Changes from 1 commit
28f00e1
55cc7a7
80f1d4a
490cecb
cb034ab
6c68bf2
a8df89b
03ef7cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,10 +22,27 @@ jobs: | |
with: | ||
command: bootimage | ||
args: --target=x86_64-mycelium.json | ||
- name: Test | ||
|
||
test: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v1 | ||
- name: install rust toolchain | ||
uses: actions-rs/toolchain@v1 | ||
with: | ||
profile: minimal | ||
toolchain: nightly | ||
components: rust-src, llvm-tools-preview | ||
- name: install dev env | ||
uses: actions-rs/[email protected] | ||
with: | ||
command: dev-env | ||
- name: install qemu | ||
run: sudo apt-get update && sudo apt-get install qemu | ||
- name: run tests | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would be good to have a step that runs any "regular" (i.e. not in qemu) rust tests in crates as well. i think we define some now as of #64, so it would be good for them to run on CI as well. if this was a separate job, it could run in parallel with the qemu tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, we should probably do that. We'll probably want to make sure to exclude the root There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we might have to just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I ended up just using more |
||
uses: actions-rs/[email protected] | ||
with: | ||
command: test | ||
command: test-x64 | ||
|
||
clippy: | ||
runs-on: ubuntu-latest | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -159,3 +159,22 @@ pub fn oops(cause: &dyn core::fmt::Display) -> ! { | |
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
#[repr(u32)] | ||
pub(crate) enum QemuExitCode { | ||
Success = 0x10, | ||
Failed = 0x11, | ||
} | ||
|
||
/// Exit using `isa-debug-exit`, for use in tests. | ||
/// | ||
/// NOTE: This is a temporary mechanism until we get proper shutdown implemented. | ||
#[cfg(test)] | ||
pub(crate) fn qemu_exit(exit_code: QemuExitCode) { | ||
let code = exit_code as u32; | ||
unsafe { | ||
asm!("out 0xf4, eax" :: "{eax}"(code) :: "intel","volatile"); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we stick a #[cfg(test)]
qemu_exit(QemuExitCode::Failed); at the end of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I was intending to do that, but forgot to do it before pushing. I'll add that. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,4 @@ pub mod cell; | |
pub mod error; | ||
pub mod io; | ||
pub mod sync; | ||
pub mod testing; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
use core::fmt; | ||
use core::mem; | ||
use core::slice; | ||
|
||
/// Internal definition type used for a test. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: the test runner in the kernel consumes this, so i am not sure if i would call it "internal" :P |
||
pub struct Test { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit/tioli: since this has all pub fields, should there maybe be a private There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It has to be constructed from within the macro, which is why it has all-pub fields, so I can't add a It's technically harmless to construct it anywhere you like, it won't do anything unless it's in the correct section. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, nvm, i missed that! seems like it could have a const-fn ctor but that might just be boilerplate? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think that might just end up being boilerplate. I'm inclined to just leave it as-is. |
||
pub module: &'static str, | ||
pub name: &'static str, | ||
pub run: fn() -> bool, | ||
hawkw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/// Type which may be used as a test return type. | ||
pub trait TestResult { | ||
/// Report any errors to `tracing`, then returns either `true` for a | ||
/// success, or `false` for a failure. | ||
fn report(self) -> bool; | ||
} | ||
|
||
impl TestResult for () { | ||
fn report(self) -> bool { | ||
true | ||
} | ||
} | ||
|
||
impl<T: fmt::Debug> TestResult for Result<(), T> { | ||
fn report(self) -> bool { | ||
match self { | ||
Ok(_) => true, | ||
Err(err) => { | ||
tracing::error!("FAIL {:?}", err); | ||
false | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// Declare a new test, sort-of like the `#[test]` attribute. | ||
// FIXME: Declare a `#[test]` custom attribute instead? | ||
#[macro_export] | ||
macro_rules! decl_test { | ||
(fn $name:ident $($t:tt)*) => { | ||
fn $name $($t)* | ||
|
||
// Introduce an anonymous const to avoid name conflicts. The `#[used]` | ||
// will prevent the symbol from being dropped, and `link_section` will | ||
// make it visible. | ||
const _: () = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, we can't put this behind a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that makes sense, i would really like to avoid linking these into non-test builds eventually. this is fine for now... |
||
#[used] | ||
#[link_section = "MyceliumTests"] | ||
static TEST: $crate::testing::Test = $crate::testing::Test { | ||
module: module_path!(), | ||
name: stringify!($name), | ||
run: || $crate::testing::TestResult::report($name()), | ||
}; | ||
}; | ||
} | ||
} | ||
|
||
// These symbols are auto-generated by lld (and similar linkers) for data | ||
// `link_section` sections, and are located at the beginning and end of the | ||
// section. | ||
// | ||
// The memory region between the two symbols will contain an array of `Test` | ||
// instances. | ||
extern "C" { | ||
static __start_MyceliumTests: (); | ||
static __stop_MyceliumTests: (); | ||
} | ||
|
||
/// Get a list of test objects. | ||
pub fn all_tests() -> &'static [Test] { | ||
unsafe { | ||
// FIXME: These should probably be `&raw const __start_*`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup! It's a way to do field indexing without requiring the object you're taking a reference to to be valid memory. (rfc: rust-lang/rfcs#2582, tracking issue: rust-lang/rust#64490) |
||
let start: *const () = &__start_MyceliumTests; | ||
let stop: *const () = &__stop_MyceliumTests; | ||
|
||
let len_bytes = (stop as usize) - (start as usize); | ||
let len = len_bytes / mem::size_of::<Test>(); | ||
assert!(len_bytes % mem::size_of::<Test>() == 0, | ||
"Section should contain a whole number of `Test`s"); | ||
|
||
if len > 0 { | ||
slice::from_raw_parts(start as *const Test, len) | ||
} else { | ||
&[] | ||
} | ||
} | ||
} | ||
|
||
decl_test! { | ||
fn it_works() -> Result<(), ()> { | ||
tracing::info!("I'm running in a test!"); | ||
Ok(()) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be nice if we could figure out a way to use the github actions cache thingy so we don't have to install qemu over and over...not a blocket though!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't even know that was a thing, so I'm not sure I can help with that :-P
If you have some documentation for how the cache thing works to point me to, let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can figure this out later!