mirror of https://github.com/rwf2/Rocket.git
Add compile tests to contrib codegen.
This commit is contained in:
parent
7dd0c8fd02
commit
3f58ea692f
|
@ -24,3 +24,8 @@ quote = "0.6"
|
|||
[build-dependencies]
|
||||
yansi = "0.5"
|
||||
version_check = "0.1.3"
|
||||
|
||||
[dev-dependencies]
|
||||
compiletest_rs = { version = "0.3", features = ["stable"] }
|
||||
rocket = { version = "0.4.0", path = "../../core/lib" }
|
||||
rocket_contrib = { version = "0.4.0", path = "../lib", features = ["diesel_sqlite_pool"] }
|
||||
|
|
|
@ -20,8 +20,8 @@ const EXAMPLE: &str = "example: `struct MyDatabase(diesel::SqliteConnection);`";
|
|||
const ONLY_ON_STRUCTS_MSG: &str = "`database` attribute can only be used on structs";
|
||||
const ONLY_UNNAMED_FIELDS: &str = "`database` attribute can only be applied to \
|
||||
structs with exactly one unnamed field";
|
||||
const NO_GENERIC_STRUCTS: &str = "`database` attribute cannot be applied to a struct \
|
||||
with a generic type";
|
||||
const NO_GENERIC_STRUCTS: &str = "`database` attribute cannot be applied to structs \
|
||||
with generics";
|
||||
|
||||
fn parse_invocation(attr: TokenStream, input: TokenStream) -> Result<DatabaseInvocation> {
|
||||
let attr_stream2 = ::proc_macro2::TokenStream::from(attr);
|
||||
|
@ -31,7 +31,7 @@ fn parse_invocation(attr: TokenStream, input: TokenStream) -> Result<DatabaseInv
|
|||
|
||||
let input = ::syn::parse::<DeriveInput>(input).unwrap();
|
||||
if !input.generics.params.is_empty() {
|
||||
return Err(input.span().error(NO_GENERIC_STRUCTS));
|
||||
return Err(input.generics.span().error(NO_GENERIC_STRUCTS));
|
||||
}
|
||||
|
||||
let structure = match input.data {
|
||||
|
@ -56,34 +56,37 @@ fn parse_invocation(attr: TokenStream, input: TokenStream) -> Result<DatabaseInv
|
|||
})
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStream> {
|
||||
let invocation = parse_invocation(attr, input)?;
|
||||
|
||||
// Store everything we're going to need to generate code.
|
||||
let connection_type = &invocation.connection_type;
|
||||
let conn_type = &invocation.connection_type;
|
||||
let name = &invocation.db_name;
|
||||
let request_guard_type = &invocation.type_name;
|
||||
let guard_type = &invocation.type_name;
|
||||
let vis = &invocation.visibility;
|
||||
let pool_type = Ident::new(&format!("{}Pool", request_guard_type), request_guard_type.span());
|
||||
let pool_type = Ident::new(&format!("{}Pool", guard_type), guard_type.span());
|
||||
let fairing_name = format!("'{}' Database Pool", name);
|
||||
let span = conn_type.span().into();
|
||||
|
||||
// A few useful paths.
|
||||
let databases = quote!(::rocket_contrib::databases);
|
||||
let r2d2 = quote!(#databases::r2d2);
|
||||
let databases = quote_spanned!(span => ::rocket_contrib::databases);
|
||||
let Poolable = quote_spanned!(span => #databases::Poolable);
|
||||
let r2d2 = quote_spanned!(span => #databases::r2d2);
|
||||
let request = quote!(::rocket::request);
|
||||
|
||||
Ok(quote! {
|
||||
let generated_types = quote_spanned! { span =>
|
||||
/// The request guard type.
|
||||
#vis struct #request_guard_type(
|
||||
pub #r2d2::PooledConnection<<#connection_type as #databases::Poolable>::Manager>
|
||||
);
|
||||
#vis struct #guard_type(pub #r2d2::PooledConnection<<#conn_type as #Poolable>::Manager>);
|
||||
|
||||
/// The pool type.
|
||||
#vis struct #pool_type(
|
||||
#r2d2::Pool<<#connection_type as #databases::Poolable>::Manager>
|
||||
);
|
||||
#vis struct #pool_type(#r2d2::Pool<<#conn_type as #Poolable>::Manager>);
|
||||
};
|
||||
|
||||
impl #request_guard_type {
|
||||
Ok(quote! {
|
||||
#generated_types
|
||||
|
||||
impl #guard_type {
|
||||
/// Returns a fairing that initializes the associated database
|
||||
/// connection pool.
|
||||
pub fn fairing() -> impl ::rocket::fairing::Fairing {
|
||||
|
@ -91,7 +94,7 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
|
|||
|
||||
::rocket::fairing::AdHoc::on_attach(#fairing_name, |rocket| {
|
||||
let pool = #databases::database_config(#name, rocket.config())
|
||||
.map(<#connection_type>::pool);
|
||||
.map(<#conn_type>::pool);
|
||||
|
||||
match pool {
|
||||
Ok(Ok(p)) => Ok(rocket.manage(#pool_type(p))),
|
||||
|
@ -117,12 +120,12 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
|
|||
pub fn get_one(rocket: &::rocket::Rocket) -> Option<Self> {
|
||||
rocket.state::<#pool_type>()
|
||||
.and_then(|pool| pool.0.get().ok())
|
||||
.map(#request_guard_type)
|
||||
.map(#guard_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::Deref for #request_guard_type {
|
||||
type Target = #connection_type;
|
||||
impl ::std::ops::Deref for #guard_type {
|
||||
type Target = #conn_type;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
@ -130,14 +133,14 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
|
|||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::DerefMut for #request_guard_type {
|
||||
impl ::std::ops::DerefMut for #guard_type {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'r> #request::FromRequest<'a, 'r> for #request_guard_type {
|
||||
impl<'a, 'r> #request::FromRequest<'a, 'r> for #guard_type {
|
||||
type Error = ();
|
||||
|
||||
fn from_request(request: &'a #request::Request<'r>) -> #request::Outcome<Self, ()> {
|
||||
|
@ -145,7 +148,7 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
|
|||
let pool = request.guard::<::rocket::State<#pool_type>>()?;
|
||||
|
||||
match pool.0.get() {
|
||||
Ok(conn) => Outcome::Success(#request_guard_type(conn)),
|
||||
Ok(conn) => Outcome::Success(#guard_type(conn)),
|
||||
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
extern crate compiletest_rs as compiletest;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{io, fs::Metadata, time::SystemTime};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum Kind {
|
||||
#[allow(dead_code)]
|
||||
Dynamic,
|
||||
Static
|
||||
}
|
||||
|
||||
impl Kind {
|
||||
fn extension(self) -> &'static str {
|
||||
match self {
|
||||
#[cfg(windows)] Kind::Dynamic => ".dll",
|
||||
#[cfg(all(unix, target_os = "macos"))] Kind::Dynamic => ".dylib",
|
||||
#[cfg(all(unix, not(target_os = "macos")))] Kind::Dynamic => ".so",
|
||||
Kind::Static => ".rlib"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn target_path() -> PathBuf {
|
||||
#[cfg(debug_assertions)] const ENVIRONMENT: &str = "debug";
|
||||
#[cfg(not(debug_assertions))] const ENVIRONMENT: &str = "release";
|
||||
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent().unwrap().parent().unwrap()
|
||||
.join("target")
|
||||
.join(ENVIRONMENT)
|
||||
}
|
||||
|
||||
fn link_flag(flag: &str, lib: &str, rel_path: &[&str]) -> String {
|
||||
let mut path = target_path();
|
||||
for component in rel_path {
|
||||
path = path.join(component);
|
||||
}
|
||||
|
||||
format!("{} {}={}", flag, lib, path.display())
|
||||
}
|
||||
|
||||
fn best_time_for(metadata: &Metadata) -> SystemTime {
|
||||
metadata.created()
|
||||
.or_else(|_| metadata.modified())
|
||||
.or_else(|_| metadata.accessed())
|
||||
.unwrap_or_else(|_| SystemTime::now())
|
||||
}
|
||||
|
||||
fn extern_dep(name: &str, kind: Kind) -> io::Result<String> {
|
||||
let deps_root = target_path().join("deps");
|
||||
let dep_name = format!("lib{}", name);
|
||||
|
||||
let mut dep_path: Option<PathBuf> = None;
|
||||
for entry in deps_root.read_dir().expect("read_dir call failed") {
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(_) => continue
|
||||
};
|
||||
|
||||
let filename = entry.file_name();
|
||||
let filename = filename.to_string_lossy();
|
||||
let lib_name = filename.split('.').next().unwrap().split('-').next().unwrap();
|
||||
|
||||
if lib_name == dep_name && filename.ends_with(kind.extension()) {
|
||||
if let Some(ref mut existing) = dep_path {
|
||||
if best_time_for(&entry.metadata()?) > best_time_for(&existing.metadata()?) {
|
||||
*existing = entry.path().into();
|
||||
}
|
||||
} else {
|
||||
dep_path = Some(entry.path().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let dep = dep_path.ok_or_else(|| io::Error::from(io::ErrorKind::NotFound))?;
|
||||
let filename = dep.file_name().ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?;
|
||||
Ok(link_flag("--extern", name, &["deps", &filename.to_string_lossy()]))
|
||||
}
|
||||
|
||||
fn run_mode(mode: &'static str, path: &'static str) {
|
||||
let mut config = compiletest::Config::default();
|
||||
config.mode = mode.parse().expect("invalid mode");
|
||||
config.src_base = format!("tests/{}", path).into();
|
||||
config.clean_rmeta();
|
||||
|
||||
config.target_rustcflags = Some([
|
||||
link_flag("-L", "crate", &[]),
|
||||
link_flag("-L", "dependency", &["deps"]),
|
||||
extern_dep("rocket_http", Kind::Static).expect("find http dep"),
|
||||
extern_dep("rocket", Kind::Static).expect("find core dep"),
|
||||
extern_dep("rocket_contrib", Kind::Static).expect("find contrib dep"),
|
||||
].join(" "));
|
||||
|
||||
compiletest::run_tests(&config);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_test() {
|
||||
run_mode("ui", "ui-fail");
|
||||
run_mode("compile-fail", "ui-fail");
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
#[macro_use] extern crate rocket_contrib;
|
||||
|
||||
use rocket_contrib::databases::diesel;
|
||||
|
||||
#[database]
|
||||
//~^ ERROR expected string literal
|
||||
struct A(diesel::SqliteConnection);
|
||||
|
||||
#[database(1)]
|
||||
//~^ ERROR expected string literal
|
||||
struct B(diesel::SqliteConnection);
|
||||
|
||||
#[database(123)]
|
||||
//~^ ERROR expected string literal
|
||||
struct C(diesel::SqliteConnection);
|
||||
|
||||
#[database("hello" "hi")]
|
||||
//~^ ERROR expected string literal
|
||||
struct D(diesel::SqliteConnection);
|
||||
|
||||
#[database("test")]
|
||||
enum Foo { }
|
||||
//~^ ERROR on structs
|
||||
|
||||
#[database("test")]
|
||||
struct Bar(diesel::SqliteConnection, diesel::SqliteConnection);
|
||||
//~^ ERROR one unnamed field
|
||||
|
||||
#[database("test")]
|
||||
union Baz { }
|
||||
//~^ ERROR on structs
|
||||
|
||||
#[database("test")]
|
||||
struct E<'r>(&'r str);
|
||||
//~^ ERROR generics
|
||||
|
||||
#[database("test")]
|
||||
struct F<T>(T);
|
||||
//~^ ERROR generics
|
||||
|
||||
fn main() { }
|
|
@ -0,0 +1,58 @@
|
|||
error: expected string literal
|
||||
--> $DIR/database-syntax.rs:5:1
|
||||
|
|
||||
5 | #[database]
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
error: expected string literal
|
||||
--> $DIR/database-syntax.rs:9:12
|
||||
|
|
||||
9 | #[database(1)]
|
||||
| ^
|
||||
|
||||
error: expected string literal
|
||||
--> $DIR/database-syntax.rs:13:12
|
||||
|
|
||||
13 | #[database(123)]
|
||||
| ^^^
|
||||
|
||||
error: expected string literal
|
||||
--> $DIR/database-syntax.rs:17:12
|
||||
|
|
||||
17 | #[database("hello" "hi")]
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: `database` attribute can only be used on structs
|
||||
--> $DIR/database-syntax.rs:22:1
|
||||
|
|
||||
22 | enum Foo { }
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: `database` attribute can only be applied to structs with exactly one unnamed field
|
||||
--> $DIR/database-syntax.rs:26:11
|
||||
|
|
||||
26 | struct Bar(diesel::SqliteConnection, diesel::SqliteConnection);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: example: `struct MyDatabase(diesel::SqliteConnection);`
|
||||
|
||||
error: `database` attribute can only be used on structs
|
||||
--> $DIR/database-syntax.rs:30:1
|
||||
|
|
||||
30 | union Baz { }
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: `database` attribute cannot be applied to structs with generics
|
||||
--> $DIR/database-syntax.rs:34:9
|
||||
|
|
||||
34 | struct E<'r>(&'r str);
|
||||
| ^^^^
|
||||
|
||||
error: `database` attribute cannot be applied to structs with generics
|
||||
--> $DIR/database-syntax.rs:38:9
|
||||
|
|
||||
38 | struct F<T>(T);
|
||||
| ^^^
|
||||
|
||||
error: aborting due to 9 previous errors
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
extern crate rocket;
|
||||
#[macro_use] extern crate rocket_contrib;
|
||||
|
||||
struct Unknown;
|
||||
|
||||
#[database("foo")]
|
||||
struct A(Unknown);
|
||||
//~^ ERROR Unknown: rocket_contrib::databases::Poolable
|
||||
|
||||
fn main() { }
|
|
@ -0,0 +1,9 @@
|
|||
error[E0277]: the trait bound `Unknown: rocket_contrib::databases::Poolable` is not satisfied
|
||||
--> $DIR/database-types.rs:7:10
|
||||
|
|
||||
7 | struct A(Unknown);
|
||||
| ^^^^^^^ the trait `rocket_contrib::databases::Poolable` is not implemented for `Unknown`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0277`.
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
||||
# file at the top-level directory of this distribution and at
|
||||
# http://rust-lang.org/COPYRIGHT.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
# option. This file may not be copied, modified, or distributed
|
||||
# except according to those terms.
|
||||
|
||||
# A script to update the references for particular tests. The idea is
|
||||
# that you do a run, which will generate files in the build directory
|
||||
# containing the (normalized) actual output of the compiler. This
|
||||
# script will then copy that output and replace the "expected output"
|
||||
# files. You can then commit the changes.
|
||||
#
|
||||
# If you find yourself manually editing a foo.stderr file, you're
|
||||
# doing it wrong.
|
||||
|
||||
if [[ "$1" == "--help" || "$1" == "-h" || "$1" == "" || "$2" == "" ]]; then
|
||||
echo "usage: $0 <build-directory> <relative-path-to-rs-files>"
|
||||
echo ""
|
||||
echo "For example:"
|
||||
echo " $0 ../../../build/x86_64-apple-darwin/test/ui *.rs */*.rs"
|
||||
fi
|
||||
|
||||
MYDIR=$(dirname $0)
|
||||
|
||||
BUILD_DIR="$1"
|
||||
shift
|
||||
|
||||
shopt -s nullglob
|
||||
|
||||
while [[ "$1" != "" ]]; do
|
||||
for EXT in "stderr" "fixed"; do
|
||||
for OUT_NAME in $BUILD_DIR/${1%.rs}.*$EXT; do
|
||||
OUT_DIR=`dirname "$1"`
|
||||
OUT_BASE=`basename "$OUT_NAME"`
|
||||
if ! (diff $OUT_NAME $MYDIR/$OUT_DIR/$OUT_BASE >& /dev/null); then
|
||||
echo updating $MYDIR/$OUT_DIR/$OUT_BASE
|
||||
cp $OUT_NAME $MYDIR/$OUT_DIR
|
||||
fi
|
||||
done
|
||||
done
|
||||
shift
|
||||
done
|
Loading…
Reference in New Issue