From 5fa97c35da2f0eeda4321da7fb5933490b798d79 Mon Sep 17 00:00:00 2001 From: Michael Lamparski Date: Tue, 6 Feb 2018 23:03:14 -0500 Subject: [PATCH] add tests for macro trailing commas --- src/test/compile-fail/macro-comma-behavior.rs | 101 +++++ src/test/compile-fail/macro-comma-support.rs | 20 + .../run-pass/auxiliary/macro-comma-support.rs | 11 + src/test/run-pass/macro-comma-behavior.rs | 98 +++++ src/test/run-pass/macro-comma-support.rs | 371 ++++++++++++++++++ 5 files changed, 601 insertions(+) create mode 100644 src/test/compile-fail/macro-comma-behavior.rs create mode 100644 src/test/compile-fail/macro-comma-support.rs create mode 100644 src/test/run-pass/auxiliary/macro-comma-support.rs create mode 100644 src/test/run-pass/macro-comma-behavior.rs create mode 100644 src/test/run-pass/macro-comma-support.rs diff --git a/src/test/compile-fail/macro-comma-behavior.rs b/src/test/compile-fail/macro-comma-behavior.rs new file mode 100644 index 000000000000..620e57b463d1 --- /dev/null +++ b/src/test/compile-fail/macro-comma-behavior.rs @@ -0,0 +1,101 @@ +// Copyright 2018 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Companion test to the similarly-named file in run-pass. + +// compile-flags: -C debug_assertions=yes +// revisions: std core + +#![cfg_attr(core, no_std)] + +#[cfg(std)] use std::fmt; +#[cfg(core)] use core::fmt; + +// (see documentation of the similarly-named test in run-pass) +fn to_format_or_not_to_format() { + let falsum = || false; + + // assert!(true, "{}",); // see run-pass + + assert_eq!(1, 1, "{}",); + //[core]~^ ERROR no arguments + //[std]~^^ ERROR no arguments + assert_ne!(1, 2, "{}",); + //[core]~^ ERROR no arguments + //[std]~^^ ERROR no arguments + + // debug_assert!(true, "{}",); // see run-pass + + debug_assert_eq!(1, 1, "{}",); + //[core]~^ ERROR no arguments + //[std]~^^ ERROR no arguments + debug_assert_ne!(1, 2, "{}",); + //[core]~^ ERROR no arguments + //[std]~^^ ERROR no arguments + + #[cfg(std)] { + eprint!("{}",); + //[std]~^ ERROR no arguments + } + + #[cfg(std)] { + // FIXME: compile-fail says "expected error not found" even though + // rustc does emit an error + // eprintln!("{}",); + // [std]~^ ERROR no arguments + } + + #[cfg(std)] { + format!("{}",); + //[std]~^ ERROR no arguments + } + + format_args!("{}",); + //[core]~^ ERROR no arguments + //[std]~^^ ERROR no arguments + + // if falsum() { panic!("{}",); } // see run-pass + + #[cfg(std)] { + print!("{}",); + //[std]~^ ERROR no arguments + } + + #[cfg(std)] { + // FIXME: compile-fail says "expected error not found" even though + // rustc does emit an error + // println!("{}",); + // [std]~^ ERROR no arguments + } + + unimplemented!("{}",); + //[core]~^ ERROR no arguments + //[std]~^^ ERROR no arguments + + // if falsum() { unreachable!("{}",); } // see run-pass + + struct S; + impl fmt::Display for S { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}",)?; + //[core]~^ ERROR no arguments + //[std]~^^ ERROR no arguments + + // FIXME: compile-fail says "expected error not found" even though + // rustc does emit an error + // writeln!(f, "{}",)?; + // [core]~^ ERROR no arguments + // [std]~^^ ERROR no arguments + Ok(()) + } + } +} + +fn main() {} diff --git a/src/test/compile-fail/macro-comma-support.rs b/src/test/compile-fail/macro-comma-support.rs new file mode 100644 index 000000000000..e5fe9b4dd7f1 --- /dev/null +++ b/src/test/compile-fail/macro-comma-support.rs @@ -0,0 +1,20 @@ +// Copyright 2018 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// This is a companion to the similarly-named test in run-pass. +// +// It tests macros that unavoidably produce compile errors. + +fn compile_error() { + compile_error!("lel"); //~ ERROR lel + compile_error!("lel",); //~ ERROR lel +} + +fn main() {} diff --git a/src/test/run-pass/auxiliary/macro-comma-support.rs b/src/test/run-pass/auxiliary/macro-comma-support.rs new file mode 100644 index 000000000000..6eafd520a726 --- /dev/null +++ b/src/test/run-pass/auxiliary/macro-comma-support.rs @@ -0,0 +1,11 @@ +// Copyright 2018 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +() diff --git a/src/test/run-pass/macro-comma-behavior.rs b/src/test/run-pass/macro-comma-behavior.rs new file mode 100644 index 000000000000..f8065f0ff148 --- /dev/null +++ b/src/test/run-pass/macro-comma-behavior.rs @@ -0,0 +1,98 @@ +// Copyright 2018 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Ideally, any macro call with a trailing comma should behave +// identically to a call without the comma. +// +// This checks the behavior of macros with trailing commas in key +// places where regressions in behavior seem highly possible (due +// to it being e.g. a place where the addition of an argument +// causes it to go down a code path with subtly different behavior). +// +// There is a companion test in compile-fail. + +// compile-flags: --test -C debug_assertions=yes +// revisions: std core + +#![cfg_attr(core, no_std)] + +#[cfg(std)] use std::fmt; +#[cfg(core)] use core::fmt; + +// an easy mistake in the implementation of 'assert!' +// would cause this to say "explicit panic" +#[test] +#[should_panic(expected = "assertion failed")] +fn assert_1arg() { + assert!(false,); +} + +// same as 'assert_1arg' +#[test] +#[should_panic(expected = "assertion failed")] +fn debug_assert_1arg() { + debug_assert!(false,); +} + +// make sure we don't accidentally forward to `write!("text")` +#[cfg(std)] +#[test] +fn writeln_2arg() { + use fmt::Write; + + let mut s = String::new(); + writeln!(&mut s, "hi",).unwrap(); + assert_eq!(&s, "hi\n"); +} + +// A number of format_args-like macros have special-case treatment +// for a single message string, which is not formatted. +// +// This test ensures that the addition of a trailing comma does not +// suddenly cause these strings to get formatted when they otherwise +// would not be. This is an easy mistake to make by having such a macro +// accept ", $($tok:tt)*" instead of ", $($tok:tt)+" after its minimal +// set of arguments. +// +// (Example: Issue #48042) +#[test] +fn to_format_or_not_to_format() { + // ("{}" is the easiest string to test because if this gets + // sent to format_args!, it'll simply fail to compile. + // "{{}}" is an example of an input that could compile and + // produce an incorrect program, but testing the panics + // would be burdensome.) + let falsum = || false; + + assert!(true, "{}",); + + // assert_eq!(1, 1, "{}",); // see compile-fail + // assert_ne!(1, 2, "{}",); // see compile-fail + + debug_assert!(true, "{}",); + + // debug_assert_eq!(1, 1, "{}",); // see compile-fail + // debug_assert_ne!(1, 2, "{}",); // see compile-fail + // eprint!("{}",); // see compile-fail + // eprintln!("{}",); // see compile-fail + // format!("{}",); // see compile-fail + // format_args!("{}",); // see compile-fail + + if falsum() { panic!("{}",); } + + // print!("{}",); // see compile-fail + // println!("{}",); // see compile-fail + // unimplemented!("{}",); // see compile-fail + + if falsum() { unreachable!("{}",); } + + // write!(&mut stdout, "{}",); // see compile-fail + // writeln!(&mut stdout, "{}",); // see compile-fail +} diff --git a/src/test/run-pass/macro-comma-support.rs b/src/test/run-pass/macro-comma-support.rs new file mode 100644 index 000000000000..f73dfb7b3b1d --- /dev/null +++ b/src/test/run-pass/macro-comma-support.rs @@ -0,0 +1,371 @@ +// Copyright 2018 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// This is a comprehensive test of invocations with and without +// trailing commas (or other, similar optionally-trailing separators). +// Every macro is accounted for, even those not tested in this file. +// (There will be a note indicating why). +// +// The expectation is for this to be updated as new macros are added, +// or as functionality is added to existing macros. +// +// (FIXME: (please discuss in PR) is the above expectation reasonable?) + +// std and core are both tested because they may contain separate +// implementations for some macro_rules! macros as an implementation +// detail. + +// compile-flags: --test -C debug_assertions=yes +// revisions: std core + +#![cfg_attr(core, no_std)] + +#![feature(concat_idents)] + +#[cfg(std)] use std::fmt; +#[cfg(core)] use core::fmt; + +#[test] +fn assert() { + assert!(true); + assert!(true,); + assert!(true, "hello"); + assert!(true, "hello",); + assert!(true, "hello {}", "world"); + assert!(true, "hello {}", "world",); +} + +#[test] +fn assert_eq() { + assert_eq!(1, 1); + assert_eq!(1, 1,); + assert_eq!(1, 1, "hello"); + assert_eq!(1, 1, "hello",); + assert_eq!(1, 1, "hello {}", "world"); + assert_eq!(1, 1, "hello {}", "world",); +} + +#[test] +fn assert_ne() { + assert_ne!(1, 2); + assert_ne!(1, 2,); + assert_ne!(1, 2, "hello"); + assert_ne!(1, 2, "hello",); + assert_ne!(1, 2, "hello {}", "world"); + assert_ne!(1, 2, "hello {}", "world",); +} + +#[test] +fn cfg() { + let _ = cfg!(pants); + let _ = cfg!(pants,); + let _ = cfg!(pants = "pants"); + let _ = cfg!(pants = "pants",); + let _ = cfg!(all(pants)); + let _ = cfg!(all(pants),); + let _ = cfg!(all(pants,)); + let _ = cfg!(all(pants,),); +} + +#[test] +fn column() { + let _ = column!(); +} + +// compile_error! is in a companion to this test in compile-fail + +#[test] +fn concat() { + let _ = concat!(); + let _ = concat!("hello"); + let _ = concat!("hello",); + let _ = concat!("hello", " world"); + let _ = concat!("hello", " world",); +} + +#[test] +fn concat_idents() { + fn foo() {} + fn foobar() {} + + concat_idents!(foo)(); + concat_idents!(foo,)(); + concat_idents!(foo, bar)(); + concat_idents!(foo, bar,)(); +} + +#[test] +fn debug_assert() { + debug_assert!(true); + debug_assert!(true, ); + debug_assert!(true, "hello"); + debug_assert!(true, "hello",); + debug_assert!(true, "hello {}", "world"); + debug_assert!(true, "hello {}", "world",); +} + +#[test] +fn debug_assert_eq() { + debug_assert_eq!(1, 1); + debug_assert_eq!(1, 1,); + debug_assert_eq!(1, 1, "hello"); + debug_assert_eq!(1, 1, "hello",); + debug_assert_eq!(1, 1, "hello {}", "world"); + debug_assert_eq!(1, 1, "hello {}", "world",); +} + +#[test] +fn debug_assert_ne() { + debug_assert_ne!(1, 2); + debug_assert_ne!(1, 2,); + debug_assert_ne!(1, 2, "hello"); + debug_assert_ne!(1, 2, "hello",); + debug_assert_ne!(1, 2, "hello {}", "world"); + debug_assert_ne!(1, 2, "hello {}", "world",); +} + +#[test] +fn env() { + let _ = env!("PATH"); + let _ = env!("PATH",); + let _ = env!("PATH", "not found"); + let _ = env!("PATH", "not found",); +} + +#[cfg(std)] +#[test] +fn eprint() { + eprint!("hello"); + eprint!("hello",); + eprint!("hello {}", "world"); + eprint!("hello {}", "world",); +} + +#[cfg(std)] +#[test] +fn eprintln() { + eprintln!(); + eprintln!("hello"); + eprintln!("hello",); + eprintln!("hello {}", "world"); + eprintln!("hello {}", "world",); +} + +#[test] +fn file() { + let _ = file!(); +} + +#[cfg(std)] +#[test] +fn format() { + let _ = format!("hello"); + let _ = format!("hello",); + let _ = format!("hello {}", "world"); + let _ = format!("hello {}", "world",); +} + +#[test] +fn format_args() { + let _ = format_args!("hello"); + let _ = format_args!("hello",); + let _ = format_args!("hello {}", "world"); + let _ = format_args!("hello {}", "world",); +} + +#[test] +fn include() { + let _ = include!("auxiliary/macro-comma-support.rs"); + let _ = include!("auxiliary/macro-comma-support.rs",); +} + +#[test] +fn include_bytes() { + let _ = include_bytes!("auxiliary/macro-comma-support.rs"); + let _ = include_bytes!("auxiliary/macro-comma-support.rs",); +} + +#[test] +fn include_str() { + let _ = include_str!("auxiliary/macro-comma-support.rs"); + let _ = include_str!("auxiliary/macro-comma-support.rs",); +} + +#[test] +fn line() { + let _ = line!(); +} + +#[test] +fn module_path() { + let _ = module_path!(); +} + +#[test] +fn option_env() { + let _ = option_env!("PATH"); + let _ = option_env!("PATH",); +} + +#[test] +fn panic() { + // prevent 'unreachable code' warnings + let falsum = || false; + + if falsum() { panic!(); } + if falsum() { panic!("hello"); } + if falsum() { panic!("hello",); } + if falsum() { panic!("hello {}", "world"); } + if falsum() { panic!("hello {}", "world",); } +} + +#[cfg(std)] +#[test] +fn print() { + print!("hello"); + print!("hello",); + print!("hello {}", "world"); + print!("hello {}", "world",); +} + +#[cfg(std)] +#[test] +fn println() { + println!(); + println!("hello"); + println!("hello",); + println!("hello {}", "world"); + println!("hello {}", "world",); +} + +// FIXME: select! (please discuss in PR) +// +// Test cases for select! are obnoxiously large, see here: +// +// https://github.com/ExpHP/rust-macro-comma-test/blob/0062e75e01ab/src/main.rs#L190-L250 +// +// and due to other usability issues described there, it is unclear to me that it is +// going anywhere in its current state. This is a job far too big for a macro_rules! macro, +// and for as long as it exists in this form it will have many many problems far worse than +// just lack of trailing comma support. + +// stringify! is N/A + +#[cfg(std)] +#[test] +fn thread_local() { + // this has an optional trailing *semicolon* + thread_local! { + #[allow(unused)] pub static A: () = () + } + + thread_local! { + #[allow(unused)] pub static AA: () = (); + } + + thread_local! { + #[allow(unused)] pub static AAA: () = (); + #[allow(unused)] pub static AAAA: () = () + } + + thread_local! { + #[allow(unused)] pub static AAAAG: () = (); + #[allow(unused)] pub static AAAAGH: () = (); + } +} + +#[test] +fn try() { + fn inner() -> Result<(), ()> { + try!(Ok(())); + try!(Ok(()),); + Ok(()) + } + + inner().unwrap(); +} + +#[test] +fn unimplemented() { + // prevent 'unreachable code' warnings + let falsum = || false; + + if falsum() { unimplemented!(); } + if falsum() { unimplemented!("hello"); } + if falsum() { unimplemented!("hello",); } + if falsum() { unimplemented!("hello {}", "world"); } + if falsum() { unimplemented!("hello {}", "world",); } +} + +#[test] +fn unreachable() { + // prevent 'unreachable code' warnings + let falsum = || false; + + if falsum() { unreachable!(); } + if falsum() { unreachable!("hello"); } + if falsum() { unreachable!("hello",); } + if falsum() { unreachable!("hello {}", "world"); } + if falsum() { unreachable!("hello {}", "world",); } +} + +#[cfg(std)] +#[test] +fn vec() { + let _: Vec<()> = vec![]; + let _ = vec![0]; + let _ = vec![0,]; + let _ = vec![0, 1]; + let _ = vec![0, 1,]; +} + +// give a test body access to a fmt::Formatter, which seems +// to be the easiest way to use 'write!' on core. +macro_rules! test_with_formatter { + ( + #[test] + fn $fname:ident($f:ident: &mut fmt::Formatter) $block:block + ) => { + #[test] + fn $fname() { + struct Struct; + impl fmt::Display for Struct { + fn fmt(&self, $f: &mut fmt::Formatter) -> fmt::Result { + Ok($block) + } + } + + // suppress "unused" + assert!(true, "{}", Struct); + } + }; +} + +test_with_formatter! { + #[test] + fn write(f: &mut fmt::Formatter) { + let _ = write!(f, "hello"); + let _ = write!(f, "hello",); + let _ = write!(f, "hello {}", "world"); + let _ = write!(f, "hello {}", "world",); + } +} + +test_with_formatter! { + #[test] + fn writeln(f: &mut fmt::Formatter) { + let _ = writeln!(f); + let _ = writeln!(f,); + let _ = writeln!(f, "hello"); + let _ = writeln!(f, "hello",); + let _ = writeln!(f, "hello {}", "world"); + let _ = writeln!(f, "hello {}", "world",); + } +}