How to use ty in a Rust macro - unit-testing

I'm try to compose a generic solution to provide fixtures for unit testing Rust code. I have come up with a macro, which allows the user to define setup and teardown methods. Here is my solution so far:
struct FooTestFixture {
pub name : String
}
impl FooTestFixture {
fn setup() -> FooTestFixture {
FooTestFixture { name: String::from("Initialised") }
}
}
fn teardown(fixture : &mut FooTestFixture) {
fixture.name = "".to_string();
}
macro_rules! unit_test {
($name:ident $fixt:ident $expr:expr) => (
#[test]
fn $name() {
let mut $fixt : FooTestFixture = FooTestFixture::setup();
$expr;
teardown(&mut $fixt);
}
)
}
unit_test! (heap_foo_fixture_should_be_initialised_using_macro f {
assert_eq!(f.name, "Initialised");
});
This works. The only problem is, that the macro unit_test is not generic, and is bound to the fixture name FooTestFixture. This means that each test module needs to redefine this macro for every test fixture, which is not ideal. What I'd like to be able to do is to also introduce a type variable and use that type in the macro expansion. Delving more into macros I have found that there is a 'ty' item, that represents a type, and I thought I could do this ...
macro_rules! unit_test {
($name:ident $fixt:ident $ftype:ty $expr:expr) => (
#[test]
fn $name() {
let mut $fixt : $ftype = $ftype::setup();
$expr;
teardown(&mut $fixt);
}
)
}
unit_test! (heap_foo_fixture_should_be_initialised_using_macro FooTestFixture f {
assert_eq!(f.name, "Initialised");
});
However, this doesn't work and results in the following error:
src\tests\heap_fixture_with_new.rs:48:40: 48:50 error: $ftype:ty is
followed by $expr:expr, which is not allowed for ty fragments
src\tests\heap_fixture_with_new.rs:48 ($name:ident $fixt:ident
$ftype:ty $expr:expr) => (
As you can see, in the macro definition, I have replaced references to FooTestFixture with $ftype.
Is what I'm trying to achieve possible? It's almost like I'd like the macro to be generic, allowing you to pass in a type, to be used inside the macro definition.

Well I realised I didn't need ty after all. I can just specify the type as an ident parameter so the following does work:
macro_rules! unit_test {
($name:ident $fixt:ident $ftype:ident $expr:expr) => (
#[test]
fn $name() {
let mut $fixt = $ftype::setup();
$expr;
teardown(&mut $fixt);
}
)
}
unit_test! (foo_fixture_should_be_initialised_using_generic_macro f FooTestFixture {
assert_eq!(f.name, "Initialised");
});

A ty cannot be directly followed by an expr. It must be followed by a specific set of tokens:
=>
,
=
|
;
:
>
[
{
as
where
Similar restriction exists after an expr, stmt, path and pat. This was introduced in RFC 550 to future-proof potential change in Rust syntax.
To fix it you need to change your macro's pattern, e.g.
macro_rules! unit_test {
($name:ident $fixt:ident<$ftype:ty> $expr:expr) => (
// ^ ^ followed by '>' is OK
unit_test! (test_name fixture_name<FooTestFixture> f {
// ^ ^

Related

Types defined in src/main.rs are not recognized in tests/test.rs file

I tried to write a unit test in rust, but when I run cargo test I get the following error:
"use of undeclared type Rating".
In the src/main.rs file I have defined the struct Rating like this:
#[derive(PartialEq, Debug, Clone, Copy)]
struct Rating(i8);
impl Rating {
pub fn new(value: i32) -> Result <Rating, CreationError> {
match value {
v if v > 10 => Err(CreationError::PosOverflow),
v if v < -10 => Err(CreationError::NegOverflow),
_ => Ok(Rating(value as i8)),
}
}
}
My test file tests/test.rs looks like this:
#[cfg(test)]
fn create_new_rating() {
assert_eq!(Rating::new(10).0, 10);
}
In the Rust documentation I only found examples where libs are tested but not binarys. Do I have to use a different syntax in this case?
Your tests folder is for integration tests, and needs to use your crate as though it were an external user. Add use mycratename::Rating to the top of the test.rs, and make Rating public.
If this is a unit test (which this looks like), it is idiomatic to put tests in the same file as the code. This is described in the Book. You would end up with something like:
#[derive(PartialEq, Debug, Clone, Copy)]
struct Rating(i8);
impl Rating {
pub fn new(value: i32) -> Result <Rating, CreationError> {
match value {
v if v > 10 => Err(CreationError::PosOverflow),
v if v < -10 => Err(CreationError::NegOverflow),
_ => Ok(Rating(value as i8)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_new_rating() {
assert_eq!(Rating::new(10).0, 10);
}
}

How to write custom ppx decorator to rescript?

I need to generate a value with a different type from my passed type. This is the first time I write on ocaml-like, and for example, in a familiar me haskell I would use Data.Generics.
How I have understood I need to use decorator and ppx. I wrote simple example
let recordHandler = (loc: Location.t, _recFlag: rec_flag, _t: type_declaration, fields: list(label_declaration)) => {
let (module Builder) = Ast_builder.make(loc);
let test = [%str
let schema: Schema = { name: "", _type: String, properties: [] }
]
let moduleExpr = Builder.pmod_structure(test);
[%str
module S = [%m moduleExpr]
]
}
let str_gen = (~loc, ~path as _, (_rec: rec_flag, t: list(type_declaration))) => {
let t = List.hd(t)
switch t.ptype_kind {
| Ptype_record(fields) => recordHandler(loc, _rec, t, fields);
| _ => Location.raise_errorf(~loc, "schema is used only for records.");
};
};
let name = "my_schema";
let () = {
let str_type_decl = Deriving.Generator.make_noarg(str_gen);
Deriving.add(name, ~str_type_decl) |> Deriving.ignore;
};
And
open Ppxlib;
let _ = Driver.run_as_ppx_rewriter()
But in using in rescript code
module User = {
#deriving(my_schema)
type my_typ = {
foo: int,
};
};
I caught:
schema is not supported
. And I made myself sure me to connect it right when I had changed #deriving(my_schema) for #deriving(abcd) and #deriving(sschema).
I got different error
Ppxlib.Deriving: 'abcd' is not a supported type deriving generator.
And my last experiment was to copy past existing library deriving accessors .
ppx_accessor
I copied-pasted it and renamed for accessors_2. And I got same error such as experiment.
accessors_2 is not supported
Also I haven't found examples "ppx rescript". Can you please help me.
What am I doing wrong (ALL , I know)
I have found answer in the article
Dropping support for custom PPXes such as ppx_deriving (the deriving
attribute is now exclusively interpreted as bs.deriving)

Regex or Wildcard in Kotlin's when statement?

I'm working on a RESTful app in Kotlin and for the router, I'm using a when statement, as it's the most readable and good looking conditional.
Is there a way to use Regex or a wildcard in the when statement for a string?
(So that URIs like "/article/get/" would all be passed to the same controller)
The structure of my router is as follows:
when(uri) {
"some/url" -> return SomeController(config).someAction(session)
}
Yes.
import kotlin.text.regex
val regex1 = Regex( /* pattern */ )
val regex2 = Regex( /* pattern */ )
/* etc */
when {
regex1.matches(uri) -> /* do stuff */
regex2.matches(uri) -> /* do stuff */
/* etc */
}
You could also use containsMatchIn if that suits your needs better than matches.
Explanation:
The test expression of a when statement is optional. If no test expression is included, then the when statement functions like an if-else if chain, where the whenCondition of each whenEntry shall independently evaluate to a boolean.
EDIT:
So I thought about it for awhile, and I came up with a different approach that might be closer to what you want.
import kotlin.text.regex
when (RegexWhenArgument(uri)) {
Regex(/* pattern */) -> /* do stuff */
Regex(/* pattern */) -> /* do stuff */
/* etc */
}
Where RegexWhenArgument is minimally defined as:
class RegexWhenArgument (val whenArgument: CharSequence) {
operator fun equals(whenEntry: Regex) = whenEntry.matches(whenArgument)
override operator fun equals(whenEntry: Any?) = (whenArgument == whenEntry)
}
This approach lets you get as close as possible to the "argument-ful" when expression syntax. I think it's about as streamlined and readable as it can be (assuming that you define the RegexWhenArgument class elsewhere).
This approach uses something similar to the visitor design pattern in combination with Kotlin's operator overloading to redefine what constitutes a "match" between a when expression argument and a whenEntry. If you really wanted to, I suppose you could take this approach a step further and generify RegexWhenArgument into a general-purpose WhenArgument and WhenArgumentDecorator that allows you to specify custom "match" criteria in a when expression for any sort of type, not just Regex.
The typing of the when statement enforces to have compatible types between the whenSubject and the whenEntries. So we cannot compare a String whenSubject with a Regex directly.
We can use when with no subject, then branch conditions may be simply boolean expressions.
fun main() {
val uri: String? = "http://my.site.com/a/b/c"
val res = when {
uri == null -> "NULL"
uri == "http://my.site.com/" -> "ROOT"
uri.startsWith("http://my.site.com/a/") -> "A STUFF"
uri.matches(Regex("http://my.site.com/b/.*")) -> "B STUFF"
else -> "DEFAULT"
}
/* do stuff */
}
Alternatively, we can emulate a kind of when+regex with a dedicated class and few helper functions.
fun main() {
val uri: String? = "http://my.site.com/a/b/c"
val res2 = when(matching(uri)) {
null -> "NULL"
matchesLiteral("http://my.site.com/") -> "ROOT"
matchesRegex("http://my.site.com/a/.*") -> "A STUFF"
else -> "DEFAULT"
}
/* do stuff */
}
class MatchLiteralOrPattern(val value: String, val isPattern: Boolean) {
override fun equals(other: Any?): Boolean {
if (other !is MatchLiteralOrPattern) return false
if (isPattern && !other.isPattern) return Regex(this.value).matches(other.value)
if (!isPattern && other.isPattern) return Regex(other.value).matches(this.value)
return value == other.value
}
}
fun matching(whenSubject: String?) = whenSubject?.let { MatchLiteralOrPattern(it, false) }
fun matchesLiteral(value: String) = MatchLiteralOrPattern(value, false)
fun matchesRegex(value: String) = MatchLiteralOrPattern(value, true)
I tried the following on the kotlin playground and it seems to work as expected.
class WhenArgument (val whenArg: CharSequence) {
override operator fun equals(other: Any?): Boolean {
return when (other) {
is Regex -> other.matches(whenArg)
else -> whenArg.equals(other)
}
}
}
fun what(target: String): String {
return when (WhenArgument(target) as Any) {
Regex("source-.*") -> "${target} is-a-source"
Regex(".*-foo") -> "${target} is-a-foo"
"target-fool" -> "${target} is-the-target-fool"
else -> "nothing"
}
}
fun main() {
println(what("target-foo"))
println(what("source-foo"))
println(what("target-bar"))
println(what("target-fool"))
}
It works around the type compatibility problem by making the 'when' argument of type Any.

Cannot use `replace_all` from the regex crate: expected (), found String

I'm trying to find and replace all instances of a string with a shortened version, and I want to maintain references to a capture if it's found.
I've written this code:
extern crate regex;
use regex::{Regex, Captures};
//... get buffer from stdin
let re = Regex::new(r"(capture something1) and (capture 2)").unwrap();
let out = re.replace_all(&buffer, |caps: &Captures| {
if let ref = caps.at(2).unwrap().to_owned() {
refs.push(ref.to_owned());
}
caps.at(1).unwrap().to_owned();
});
Unfortunately compilation fails with the error:
src/bin/remove_links.rs:16:18: 16:29 error: type mismatch resolving `for<'r, 'r> <[closure#src/bin/remove_links.rs:16:39: 22:6] as std::ops::FnOnce<(&'r regex::Captures<'r>,)>>::Output == std::string::String`:
expected (),
found struct `std::string::String` [E0271]
src/bin/remove_links.rs:16 let out = re.replace_all(&buffer, |caps: &Captures| {
^~~~~~~~~~~
src/bin/remove_links.rs:16:18: 16:29 help: run `rustc --explain E0271` to see a detailed explanation
src/bin/remove_links.rs:16:18: 16:29 note: required because of the requirements on the impl of `regex::Replacer` for `[closure#src/bin/remove_links.rs:16:39: 22:6]`
I can't make sense of it. I've also tried adding use regex::{Regex, Captures, Replacer} but that doesn't change the error at all.
As #BurntSushi5 pointed out, your closure should return a String. Here is a complete example for future reference:
extern crate regex;
use regex::{Regex, Captures};
fn main() {
let buffer = "abcdef";
let re = Regex::new(r"(\w)bc(\w)").unwrap();
let out = re.replace_all(&buffer, |caps: &Captures| {
caps.at(1).unwrap().to_owned()
});
println!("{:?}", out); // => "aef"
}

How can I test Rust methods that depend on environment variables?

I am building a library that interrogates its running environment to return values to the asking program. Sometimes as simple as
pub fn func_name() -> Option<String> {
match env::var("ENVIRONMENT_VARIABLE") {
Ok(s) => Some(s),
Err(e) => None
}
}
but sometimes a good bit more complicated, or even having a result composed of various environment variables. How can I test that these methods are functioning as expected?
"How do I test X" is almost always answered with "by controlling X". In this case, you need to control the environment variables:
use std::env;
fn env_is_set() -> bool {
match env::var("ENVIRONMENT_VARIABLE") {
Ok(s) => s == "yes",
_ => false
}
}
#[test]
fn when_set_yes() {
env::set_var("ENVIRONMENT_VARIABLE", "yes");
assert!(env_is_set());
}
#[test]
fn when_set_no() {
env::set_var("ENVIRONMENT_VARIABLE", "no");
assert!(!env_is_set());
}
#[test]
fn when_unset() {
env::remove_var("ENVIRONMENT_VARIABLE");
assert!(!env_is_set());
}
However, you need to be aware that environment variables are a shared resource. From the docs for set_var, emphasis mine:
Sets the environment variable k to the value v for the currently running process.
You may also need to be aware that the Rust test runner runs tests in parallel by default, so it's possible to have one test clobber another.
Additionally, you may wish to "reset" your environment variables to a known good state after the test.
Your other option (if you don't want to mess around with actually setting environment variables) is to abstract the call away. I am only just learning Rust and so I am not sure if this is "the Rust way(tm)" to do it... but this is certainly how I would do it in another language/environment:
use std::env;
pub trait QueryEnvironment {
fn get_var(&self, var: &str) -> Result<String, std::env::VarError>;
}
struct MockQuery;
struct ActualQuery;
impl QueryEnvironment for MockQuery {
fn get_var(&self, _var: &str) -> Result<String, std::env::VarError> {
Ok("Some Mocked Result".to_string()) // Returns a mocked response
}
}
impl QueryEnvironment for ActualQuery {
fn get_var(&self, var: &str) -> Result<String, std::env::VarError> {
env::var(var) // Returns an actual response
}
}
fn main() {
env::set_var("ENVIRONMENT_VARIABLE", "user"); // Just to make program execute for ActualQuery type
let mocked_query = MockQuery;
let actual_query = ActualQuery;
println!("The mocked environment value is: {}", func_name(mocked_query).unwrap());
println!("The actual environment value is: {}", func_name(actual_query).unwrap());
}
pub fn func_name<T: QueryEnvironment>(query: T) -> Option<String> {
match query.get_var("ENVIRONMENT_VARIABLE") {
Ok(s) => Some(s),
Err(_) => None
}
}
Example on the rust playground
Notice how the actual call panics. This is the implementation you would use in actual code. For your tests, you would use the mocked ones.
EDIT:
The test helpers below are now available in a dedicated crate
Disclaimer: I'm a co-author
I had the same need and implemented some small test helpers which take care of the caveats mentioned by #Shepmaster .
These test helpers enable testing like so:
#[test]
fn test_default_log_level_is_info() {
with_env_vars(
vec![
("LOGLEVEL", None),
("SOME_OTHER_VAR", Some("foo"))
],
|| {
let actual = Config::new();
assert_eq!("INFO", actual.log_level);
},
);
}
with_env_vars will take care of:
Avoiding side effects when running tests in parallel
Resetting the env variables to their original values when the test closure completes
Support for unsetting environment variables during the test closure
All of the above, also when the test-closure panics.
The helper:
use lazy_static::lazy_static;
use std::env::VarError;
use std::panic::{RefUnwindSafe, UnwindSafe};
use std::sync::Mutex;
use std::{env, panic};
lazy_static! {
static ref SERIAL_TEST: Mutex<()> = Default::default();
}
/// Sets environment variables to the given value for the duration of the closure.
/// Restores the previous values when the closure completes or panics, before unwinding the panic.
pub fn with_env_vars<F>(kvs: Vec<(&str, Option<&str>)>, closure: F)
where
F: Fn() + UnwindSafe + RefUnwindSafe,
{
let guard = SERIAL_TEST.lock().unwrap();
let mut old_kvs: Vec<(&str, Result<String, VarError>)> = Vec::new();
for (k, v) in kvs {
let old_v = env::var(k);
old_kvs.push((k, old_v));
match v {
None => env::remove_var(k),
Some(v) => env::set_var(k, v),
}
}
match panic::catch_unwind(|| {
closure();
}) {
Ok(_) => {
for (k, v) in old_kvs {
reset_env(k, v);
}
}
Err(err) => {
for (k, v) in old_kvs {
reset_env(k, v);
}
drop(guard);
panic::resume_unwind(err);
}
};
}
fn reset_env(k: &str, old: Result<String, VarError>) {
if let Ok(v) = old {
env::set_var(k, v);
} else {
env::remove_var(k);
}
}
A third option, and one I think is better, is to pass in the existing type - rather than creating a new abstraction that everyone would have to coerce to.
pub fn new<I>(vars: I)
where I: Iterator<Item = (String, String)>
{
for (x, y) in vars {
println!("{}: {}", x, y)
}
}
#[test]
fn trivial_call() {
let vars = [("fred".to_string(), "jones".to_string())];
new(vars.iter().cloned());
}
Thanks to qrlpz on #rust for helping me get this sorted for my program, just sharing the result to help others :)