Skip to content

Commit

Permalink
Add regression tests to ensure stable drop order
Browse files Browse the repository at this point in the history
  • Loading branch information
aochagavia committed Jul 12, 2017
1 parent 1e5162c commit f86e433
Showing 1 changed file with 231 additions and 0 deletions.
231 changes: 231 additions & 0 deletions src/test/run-pass/rfc1857-drop-order.rs
@@ -0,0 +1,231 @@
// Copyright 2017 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.

#![allow(dead_code, unreachable_code)]

use std::cell::RefCell;
use std::rc::Rc;
use std::panic::{self, AssertUnwindSafe, UnwindSafe};

// This struct is used to record the order in which elements are dropped
struct PushOnDrop {
vec: Rc<RefCell<Vec<u32>>>,
val: u32
}

impl PushOnDrop {
fn new(val: u32, vec: Rc<RefCell<Vec<u32>>>) -> PushOnDrop {
PushOnDrop { vec, val }
}
}

impl Drop for PushOnDrop {
fn drop(&mut self) {
self.vec.borrow_mut().push(self.val)
}
}

impl UnwindSafe for PushOnDrop { }

// Structs
struct TestStruct {
x: PushOnDrop,
y: PushOnDrop,
z: PushOnDrop
}

// Tuple structs
struct TestTupleStruct(PushOnDrop, PushOnDrop, PushOnDrop);

// Enum variants
enum TestEnum {
Tuple(PushOnDrop, PushOnDrop, PushOnDrop),
Struct { x: PushOnDrop, y: PushOnDrop, z: PushOnDrop }
}

fn test_drop_tuple() {
// Tuple fields are dropped in the same order they are declared
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let test_tuple = (PushOnDrop::new(1, dropped_fields.clone()),
PushOnDrop::new(2, dropped_fields.clone()));
drop(test_tuple);
assert_eq!(*dropped_fields.borrow(), &[1, 2]);

// Panic during construction means that fields are treated as local variables
// Therefore they are dropped in reverse order of initialization
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let cloned = AssertUnwindSafe(dropped_fields.clone());
panic::catch_unwind(|| {
(PushOnDrop::new(2, cloned.clone()),
PushOnDrop::new(1, cloned.clone()),
panic!("this panic is catched :D"));
}).err().unwrap();
assert_eq!(*dropped_fields.borrow(), &[1, 2]);
}

fn test_drop_struct() {
// Struct fields are dropped in the same order they are declared
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let test_struct = TestStruct {
x: PushOnDrop::new(1, dropped_fields.clone()),
y: PushOnDrop::new(2, dropped_fields.clone()),
z: PushOnDrop::new(3, dropped_fields.clone()),
};
drop(test_struct);
assert_eq!(*dropped_fields.borrow(), &[1, 2, 3]);

// The same holds for tuple structs
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let test_tuple_struct = TestTupleStruct(PushOnDrop::new(1, dropped_fields.clone()),
PushOnDrop::new(2, dropped_fields.clone()),
PushOnDrop::new(3, dropped_fields.clone()));
drop(test_tuple_struct);
assert_eq!(*dropped_fields.borrow(), &[1, 2, 3]);

// Panic during struct construction means that fields are treated as local variables
// Therefore they are dropped in reverse order of initialization
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let cloned = AssertUnwindSafe(dropped_fields.clone());
panic::catch_unwind(|| {
TestStruct {
x: PushOnDrop::new(2, cloned.clone()),
y: PushOnDrop::new(1, cloned.clone()),
z: panic!("this panic is catched :D")
};
}).err().unwrap();
assert_eq!(*dropped_fields.borrow(), &[1, 2]);

// Test with different initialization order
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let cloned = AssertUnwindSafe(dropped_fields.clone());
panic::catch_unwind(|| {
TestStruct {
y: PushOnDrop::new(2, cloned.clone()),
x: PushOnDrop::new(1, cloned.clone()),
z: panic!("this panic is catched :D")
};
}).err().unwrap();
assert_eq!(*dropped_fields.borrow(), &[1, 2]);

// The same holds for tuple structs
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let cloned = AssertUnwindSafe(dropped_fields.clone());
panic::catch_unwind(|| {
TestTupleStruct(PushOnDrop::new(2, cloned.clone()),
PushOnDrop::new(1, cloned.clone()),
panic!("this panic is catched :D"));
}).err().unwrap();
assert_eq!(*dropped_fields.borrow(), &[1, 2]);
}

fn test_drop_enum() {
// Enum variants are dropped in the same order they are declared
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let test_struct_enum = TestEnum::Struct {
x: PushOnDrop::new(1, dropped_fields.clone()),
y: PushOnDrop::new(2, dropped_fields.clone()),
z: PushOnDrop::new(3, dropped_fields.clone())
};
drop(test_struct_enum);
assert_eq!(*dropped_fields.borrow(), &[1, 2, 3]);

// The same holds for tuple enum variants
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let test_tuple_enum = TestEnum::Tuple(PushOnDrop::new(1, dropped_fields.clone()),
PushOnDrop::new(2, dropped_fields.clone()),
PushOnDrop::new(3, dropped_fields.clone()));
drop(test_tuple_enum);
assert_eq!(*dropped_fields.borrow(), &[1, 2, 3]);

// Panic during enum construction means that fields are treated as local variables
// Therefore they are dropped in reverse order of initialization
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let cloned = AssertUnwindSafe(dropped_fields.clone());
panic::catch_unwind(|| {
TestEnum::Struct {
x: PushOnDrop::new(2, cloned.clone()),
y: PushOnDrop::new(1, cloned.clone()),
z: panic!("this panic is catched :D")
};
}).err().unwrap();
assert_eq!(*dropped_fields.borrow(), &[1, 2]);

// Test with different initialization order
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let cloned = AssertUnwindSafe(dropped_fields.clone());
panic::catch_unwind(|| {
TestEnum::Struct {
y: PushOnDrop::new(2, cloned.clone()),
x: PushOnDrop::new(1, cloned.clone()),
z: panic!("this panic is catched :D")
};
}).err().unwrap();
assert_eq!(*dropped_fields.borrow(), &[1, 2]);

// The same holds for tuple enum variants
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let cloned = AssertUnwindSafe(dropped_fields.clone());
panic::catch_unwind(|| {
TestEnum::Tuple(PushOnDrop::new(2, cloned.clone()),
PushOnDrop::new(1, cloned.clone()),
panic!("this panic is catched :D"));
}).err().unwrap();
assert_eq!(*dropped_fields.borrow(), &[1, 2]);
}

fn test_drop_list() {
// Elements in a Vec are dropped in the same order they are pushed
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let xs = vec![PushOnDrop::new(1, dropped_fields.clone()),
PushOnDrop::new(2, dropped_fields.clone()),
PushOnDrop::new(3, dropped_fields.clone())];
drop(xs);
assert_eq!(*dropped_fields.borrow(), &[1, 2, 3]);

// The same holds for arrays
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let xs = [PushOnDrop::new(1, dropped_fields.clone()),
PushOnDrop::new(2, dropped_fields.clone()),
PushOnDrop::new(3, dropped_fields.clone())];
drop(xs);
assert_eq!(*dropped_fields.borrow(), &[1, 2, 3]);

// Panic during vec construction means that fields are treated as local variables
// Therefore they are dropped in reverse order of initialization
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let cloned = AssertUnwindSafe(dropped_fields.clone());
panic::catch_unwind(|| {
vec![
PushOnDrop::new(2, cloned.clone()),
PushOnDrop::new(1, cloned.clone()),
panic!("this panic is catched :D")
];
}).err().unwrap();
assert_eq!(*dropped_fields.borrow(), &[1, 2]);

// The same holds for arrays
let dropped_fields = Rc::new(RefCell::new(Vec::new()));
let cloned = AssertUnwindSafe(dropped_fields.clone());
panic::catch_unwind(|| {
[
PushOnDrop::new(2, cloned.clone()),
PushOnDrop::new(1, cloned.clone()),
panic!("this panic is catched :D")
];
}).err().unwrap();
assert_eq!(*dropped_fields.borrow(), &[1, 2]);
}

fn main() {
test_drop_tuple();
test_drop_struct();
test_drop_enum();
test_drop_list();
}

0 comments on commit f86e433

Please sign in to comment.