Skip to content

Commit 496577c

Browse files
committed
rune: Improve test case macros and docs
1 parent 982f9a8 commit 496577c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+671
-805
lines changed

crates/rune/src/compile/options.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pub struct Options {
128128

129129
impl Options {
130130
/// The default options.
131-
const DEFAULT: Options = Options {
131+
pub(crate) const DEFAULT: Options = Options {
132132
link_checks: true,
133133
memoize_instance_fn: true,
134134
debug_info: true,
@@ -442,4 +442,17 @@ impl Options {
442442
pub fn memoize_instance_fn(&mut self, enabled: bool) {
443443
self.memoize_instance_fn = enabled;
444444
}
445+
446+
/// Whether to build sources as scripts where the source is executed like a
447+
/// function body.
448+
pub fn script(&mut self, enabled: bool) {
449+
self.function_body = enabled;
450+
}
451+
}
452+
453+
impl Default for Options {
454+
#[inline]
455+
fn default() -> Self {
456+
Options::DEFAULT
457+
}
445458
}

crates/rune/src/modules/collections/hash_map.rs

+72
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,78 @@ pub fn module() -> Result<Module, ContextError> {
6161
Ok(m)
6262
}
6363

64+
/// A [hash map] implemented with quadratic probing and SIMD lookup.
65+
///
66+
/// By default, `HashMap` uses a hashing algorithm selected to provide
67+
/// resistance against HashDoS attacks. The algorithm is randomly seeded, and a
68+
/// reasonable best-effort is made to generate this seed from a high quality,
69+
/// secure source of randomness provided by the host without blocking the
70+
/// program. Because of this, the randomness of the seed depends on the output
71+
/// quality of the system's random number coroutine when the seed is created. In
72+
/// particular, seeds generated when the system's entropy pool is abnormally low
73+
/// such as during system boot may be of a lower quality.
74+
///
75+
/// The default hashing algorithm is currently SipHash 1-3, though this is
76+
/// subject to change at any point in the future. While its performance is very
77+
/// competitive for medium sized keys, other hashing algorithms will outperform
78+
/// it for small keys such as integers as well as large keys such as long
79+
/// strings, though those algorithms will typically *not* protect against
80+
/// attacks such as HashDoS.
81+
///
82+
/// The hashing algorithm can be replaced on a per-`HashMap` basis using the
83+
/// [`default`], [`with_hasher`], and [`with_capacity_and_hasher`] methods.
84+
/// There are many alternative [hashing algorithms available on crates.io].
85+
///
86+
/// It is required that the keys implement the [`EQ`] and [`HASH`] protocols. If
87+
/// you implement these yourself, it is important that the following property
88+
/// holds:
89+
///
90+
/// ```text
91+
/// k1 == k2 -> hash(k1) == hash(k2)
92+
/// ```
93+
///
94+
/// In other words, if two keys are equal, their hashes must be equal. Violating
95+
/// this property is a logic error.
96+
///
97+
/// It is also a logic error for a key to be modified in such a way that the
98+
/// key's hash, as determined by the [`HASH`] protocol, or its equality, as
99+
/// determined by the [`EQ`] protocol, changes while it is in the map. This is
100+
/// normally only possible through [`Cell`], [`RefCell`], global state, I/O, or
101+
/// unsafe code.
102+
///
103+
/// The behavior resulting from either logic error is not specified, but will be
104+
/// encapsulated to the `HashMap` that observed the logic error and not result
105+
/// in undefined behavior. This could include panics, incorrect results, aborts,
106+
/// memory leaks, and non-termination.
107+
///
108+
/// The hash table implementation is a Rust port of Google's [SwissTable]. The
109+
/// original C++ version of SwissTable can be found [here], and this [CppCon
110+
/// talk] gives an overview of how the algorithm works.
111+
///
112+
/// [hash map]: crate::collections#use-a-hashmap-when
113+
/// [hashing algorithms available on crates.io]: https://crates.io/keywords/hasher
114+
/// [SwissTable]: https://abseil.io/blog/20180927-swisstables
115+
/// [here]: https://github.com/abseil/abseil-cpp/blob/master/absl/container/internal/raw_hash_set.h
116+
/// [CppCon talk]: https://www.youtube.com/watch?v=ncHmEUmJZf4
117+
///
118+
/// # Examples
119+
///
120+
/// ```rune
121+
/// use std::collections::HashMap;
122+
///
123+
/// enum Tile {
124+
/// Wall,
125+
/// }
126+
///
127+
/// let m = HashMap::new();
128+
///
129+
/// m.insert((0, 1), Tile::Wall);
130+
/// m[(0, 3)] = 5;
131+
///
132+
/// assert_eq!(m.get((0, 1)), Some(Tile::Wall));
133+
/// assert_eq!(m.get((0, 2)), None);
134+
/// assert_eq!(m[(0, 3)], 5);
135+
/// ```
64136
#[derive(Any)]
65137
#[rune(item = ::std::collections::hash_map)]
66138
pub(crate) struct HashMap {

crates/rune/src/modules/collections/hash_set.rs

+43
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,49 @@ pub fn module() -> Result<Module, ContextError> {
6666
Ok(m)
6767
}
6868

69+
/// A [hash set] implemented as a `HashMap` where the value is `()`.
70+
///
71+
/// As with the [`HashMap`] type, a `HashSet` requires that the elements
72+
/// implement the [`EQ`] and [`HASH`] protocols. If you implement these
73+
/// yourself, it is important that the following property holds:
74+
///
75+
/// ```text
76+
/// k1 == k2 -> hash(k1) == hash(k2)
77+
/// ```
78+
///
79+
/// In other words, if two keys are equal, their hashes must be equal. Violating
80+
/// this property is a logic error.
81+
///
82+
/// It is also a logic error for a key to be modified in such a way that the
83+
/// key's hash, as determined by the [`HASH`] protocol, or its equality, as
84+
/// determined by the [`EQ`] protocol, changes while it is in the map. This is
85+
/// normally only possible through [`Cell`], [`RefCell`], global state, I/O, or
86+
/// unsafe code.
87+
///
88+
/// The behavior resulting from either logic error is not specified, but will be
89+
/// encapsulated to the `HashSet` that observed the logic error and not result
90+
/// in undefined behavior. This could include panics, incorrect results, aborts,
91+
/// memory leaks, and non-termination.
92+
///
93+
/// [hash set]: crate::collections#use-the-set-variant-of-any-of-these-maps-when
94+
/// [`HashMap`]: crate::collections::HashMap
95+
///
96+
/// # Examples
97+
///
98+
/// ```rune
99+
/// use std::collections::HashSet;
100+
///
101+
/// enum Tile {
102+
/// Wall,
103+
/// }
104+
///
105+
/// let m = HashSet::new();
106+
///
107+
/// m.insert((0, 1));
108+
///
109+
/// assert!(m.contains((0, 1)));
110+
/// assert!(!m.contains((0, 2)));
111+
/// ```
69112
#[derive(Any)]
70113
#[rune(module = crate, item = ::std::collections::hash_set)]
71114
pub(crate) struct HashSet {

crates/rune/src/runtime/vm.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -381,11 +381,11 @@ impl Vm {
381381
/// println!("output: {}", output);
382382
/// # Ok::<_, rune::support::Error>(())
383383
/// ```
384-
pub fn execute<A, N>(&mut self, name: N, args: A) -> Result<VmExecution<&mut Self>, VmError>
385-
where
386-
N: ToTypeHash,
387-
A: Args,
388-
{
384+
pub fn execute(
385+
&mut self,
386+
name: impl ToTypeHash,
387+
args: impl Args,
388+
) -> Result<VmExecution<&mut Self>, VmError> {
389389
self.set_entrypoint(name, args.count())?;
390390
args.into_stack(&mut self.stack).into_result()?;
391391
Ok(VmExecution::new(self))
@@ -397,11 +397,11 @@ impl Vm {
397397
/// This is accomplished by preventing values escaping from being
398398
/// non-exclusively sent with the execution or escaping the execution. We
399399
/// only support encoding arguments which themselves are `Send`.
400-
pub fn send_execute<A, N>(mut self, name: N, args: A) -> Result<VmSendExecution, VmError>
401-
where
402-
N: ToTypeHash,
403-
A: Send + Args,
404-
{
400+
pub fn send_execute(
401+
mut self,
402+
name: impl ToTypeHash,
403+
args: impl Args + Send,
404+
) -> Result<VmSendExecution, VmError> {
405405
// Safety: make sure the stack is clear, preventing any values from
406406
// being sent along with the virtual machine.
407407
self.stack.clear();

crates/rune/src/tests.rs

+32-27
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub(crate) mod prelude {
2525
pub(crate) use crate::tests::{eval, run};
2626
pub(crate) use crate::{
2727
from_value, prepare, sources, span, vm_try, Any, Context, ContextError, Diagnostics,
28-
FromValue, Hash, Item, ItemBuf, Module, Source, Sources, Value, Vm,
28+
FromValue, Hash, Item, ItemBuf, Module, Options, Source, Sources, Value, Vm,
2929
};
3030
pub(crate) use futures_executor::block_on;
3131

@@ -43,11 +43,10 @@ use ::rust_alloc::sync::Arc;
4343

4444
use anyhow::{Context as _, Error, Result};
4545

46-
use crate::alloc;
47-
use crate::item::IntoComponent;
4846
use crate::runtime::{Args, VmError};
4947
use crate::{
50-
termcolor, BuildError, Context, Diagnostics, FromValue, ItemBuf, Source, Sources, Unit, Vm,
48+
alloc, termcolor, BuildError, Context, Diagnostics, FromValue, Hash, Options, Source, Sources,
49+
Unit, Vm,
5150
};
5251

5352
/// An error that can be raised during testing.
@@ -97,9 +96,13 @@ pub fn compile_helper(source: &str, diagnostics: &mut Diagnostics) -> Result<Uni
9796
let mut sources = Sources::new();
9897
sources.insert(Source::new("main", source)?)?;
9998

99+
let mut options = Options::default();
100+
options.script(true);
101+
100102
let unit = crate::prepare(&mut sources)
101103
.with_context(&context)
102104
.with_diagnostics(diagnostics)
105+
.with_options(&options)
103106
.build()?;
104107

105108
Ok(unit)
@@ -111,10 +114,18 @@ pub fn vm(
111114
context: &Context,
112115
sources: &mut Sources,
113116
diagnostics: &mut Diagnostics,
117+
script: bool,
114118
) -> Result<Vm, TestError> {
119+
let mut options = Options::default();
120+
121+
if script {
122+
options.script(true);
123+
}
124+
115125
let result = crate::prepare(sources)
116126
.with_context(context)
117127
.with_diagnostics(diagnostics)
128+
.with_options(&options)
118129
.build();
119130

120131
let Ok(unit) = result else {
@@ -134,24 +145,23 @@ pub fn vm(
134145

135146
/// Call the specified function in the given script sources.
136147
#[doc(hidden)]
137-
pub fn run_helper<N, A, T>(
148+
pub fn run_helper<T>(
138149
context: &Context,
139150
sources: &mut Sources,
140151
diagnostics: &mut Diagnostics,
141-
function: N,
142-
args: A,
152+
args: impl Args,
153+
script: bool,
143154
) -> Result<T, TestError>
144155
where
145-
N: IntoIterator,
146-
N::Item: IntoComponent,
147-
A: Args,
148156
T: FromValue,
149157
{
150-
let mut vm = vm(context, sources, diagnostics)?;
158+
let mut vm = vm(context, sources, diagnostics, script)?;
151159

152-
let item = ItemBuf::with_item(function)?;
153-
154-
let mut execute = vm.execute(&item, args).map_err(TestError::VmError)?;
160+
let mut execute = if script {
161+
vm.execute(Hash::EMPTY, args).map_err(TestError::VmError)?
162+
} else {
163+
vm.execute(["main"], args).map_err(TestError::VmError)?
164+
};
155165

156166
let output = ::futures_executor::block_on(execute.async_complete())
157167
.into_result()
@@ -170,19 +180,16 @@ pub fn sources(source: &str) -> Sources {
170180
}
171181

172182
/// Run the given source with diagnostics being printed to stderr.
173-
pub fn run<N, A, T>(context: &Context, source: &str, function: N, args: A) -> Result<T>
183+
pub fn run<T>(context: &Context, source: &str, args: impl Args, script: bool) -> Result<T>
174184
where
175-
N: IntoIterator,
176-
N::Item: IntoComponent,
177-
A: Args,
178185
T: FromValue,
179186
{
180187
let mut sources = Sources::new();
181-
sources.insert(Source::new("main", source)?)?;
188+
sources.insert(Source::memory(source)?)?;
182189

183190
let mut diagnostics = Default::default();
184191

185-
let e = match run_helper(context, &mut sources, &mut diagnostics, function, args) {
192+
let e = match run_helper(context, &mut sources, &mut diagnostics, args, script) {
186193
Ok(value) => return Ok(value),
187194
Err(e) => e,
188195
};
@@ -229,7 +236,7 @@ where
229236
let source = source.as_ref();
230237
let context = Context::with_default_modules().expect("Failed to build context");
231238

232-
match run(&context, source, ["main"], ()) {
239+
match run(&context, source, (), true) {
233240
Ok(output) => output,
234241
Err(error) => {
235242
panic!("Program failed to run:\n{error}\n{source}");
@@ -257,10 +264,10 @@ macro_rules! rune_assert {
257264
/// of native Rust data. This also accepts a tuple of arguments in the second
258265
/// position, to pass native objects as arguments to the script.
259266
macro_rules! rune_n {
260-
($module:expr, $args:expr, $ty:ty => $($tt:tt)*) => {{
267+
($(mod $module:expr,)* $args:expr, $($tt:tt)*) => {{
261268
let mut context = $crate::Context::with_default_modules().expect("Failed to build context");
262-
context.install($module).expect("Failed to install native module");
263-
$crate::tests::run::<_, _, $ty>(&context, stringify!($($tt)*), ["main"], $args).expect("Program ran unsuccessfully")
269+
$(context.install(&$module).expect("Failed to install native module");)*
270+
$crate::tests::run(&context, stringify!($($tt)*), $args, false).expect("Program ran unsuccessfully")
264271
}};
265272
}
266273

@@ -277,7 +284,7 @@ macro_rules! assert_vm_error {
277284
let mut diagnostics = Default::default();
278285

279286
let mut sources = $crate::tests::sources($source);
280-
let e = match $crate::tests::run_helper::<_, _, $ty>(&context, &mut sources, &mut diagnostics, ["main"], ()) {
287+
let e = match $crate::tests::run_helper::<$ty>(&context, &mut sources, &mut diagnostics, (), true) {
281288
Err(e) => e,
282289
actual => {
283290
expected!("program error", Err(e), actual, $source)
@@ -430,8 +437,6 @@ mod builtin_macros;
430437
#[cfg(not(miri))]
431438
mod capture;
432439
#[cfg(not(miri))]
433-
mod collections;
434-
#[cfg(not(miri))]
435440
mod comments;
436441
#[cfg(not(miri))]
437442
mod compiler_docs;

0 commit comments

Comments
 (0)