Add more thorough coverage tests for #[coverage(..)]
in nested functions
These tests reflect the current implementation behaviour, which is not necessarily the desired behaviour.
This commit is contained in:
parent
9a084e6310
commit
5093658632
100
tests/coverage/attr/nested.cov-map
Normal file
100
tests/coverage/attr/nested.cov-map
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
Function name: <<<nested::MyOuter as nested::MyTrait>::trait_method::MyMiddle as nested::MyTrait>::trait_method::MyInner as nested::MyTrait>::trait_method (unused)
|
||||||
|
Raw bytes (9): 0x[01, 01, 00, 01, 00, 39, 15, 02, 16]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 1
|
||||||
|
- Code(Zero) at (prev + 57, 21) to (start + 2, 22)
|
||||||
|
|
||||||
|
Function name: <<<nested::MyOuter>::outer_method::MyMiddle>::middle_method::MyInner>::inner_method (unused)
|
||||||
|
Raw bytes (9): 0x[01, 01, 00, 01, 00, 23, 15, 02, 16]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 1
|
||||||
|
- Code(Zero) at (prev + 35, 21) to (start + 2, 22)
|
||||||
|
|
||||||
|
Function name: <<nested::MyOuter as nested::MyTrait>::trait_method::MyMiddle as nested::MyTrait>::trait_method (unused)
|
||||||
|
Raw bytes (9): 0x[01, 01, 00, 01, 00, 36, 0d, 08, 0e]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 1
|
||||||
|
- Code(Zero) at (prev + 54, 13) to (start + 8, 14)
|
||||||
|
|
||||||
|
Function name: <<nested::MyOuter>::outer_method::MyMiddle>::middle_method (unused)
|
||||||
|
Raw bytes (9): 0x[01, 01, 00, 01, 00, 20, 0d, 08, 0e]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 1
|
||||||
|
- Code(Zero) at (prev + 32, 13) to (start + 8, 14)
|
||||||
|
|
||||||
|
Function name: nested::closure_expr
|
||||||
|
Raw bytes (14): 0x[01, 01, 00, 02, 01, 44, 01, 01, 0f, 01, 0b, 05, 01, 02]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 2
|
||||||
|
- Code(Counter(0)) at (prev + 68, 1) to (start + 1, 15)
|
||||||
|
- Code(Counter(0)) at (prev + 11, 5) to (start + 1, 2)
|
||||||
|
|
||||||
|
Function name: nested::closure_expr::{closure#0}::{closure#0} (unused)
|
||||||
|
Raw bytes (14): 0x[01, 01, 00, 02, 00, 47, 1a, 01, 17, 00, 04, 0d, 01, 0a]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 2
|
||||||
|
- Code(Zero) at (prev + 71, 26) to (start + 1, 23)
|
||||||
|
- Code(Zero) at (prev + 4, 13) to (start + 1, 10)
|
||||||
|
|
||||||
|
Function name: nested::closure_expr::{closure#0}::{closure#0}::{closure#0} (unused)
|
||||||
|
Raw bytes (9): 0x[01, 01, 00, 01, 00, 48, 1d, 02, 0e]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 1
|
||||||
|
- Code(Zero) at (prev + 72, 29) to (start + 2, 14)
|
||||||
|
|
||||||
|
Function name: nested::closure_tail
|
||||||
|
Raw bytes (14): 0x[01, 01, 00, 02, 01, 53, 01, 01, 0f, 01, 11, 05, 01, 02]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 2
|
||||||
|
- Code(Counter(0)) at (prev + 83, 1) to (start + 1, 15)
|
||||||
|
- Code(Counter(0)) at (prev + 17, 5) to (start + 1, 2)
|
||||||
|
|
||||||
|
Function name: nested::closure_tail::{closure#0}::{closure#0} (unused)
|
||||||
|
Raw bytes (14): 0x[01, 01, 00, 02, 00, 58, 14, 01, 1f, 00, 06, 15, 01, 12]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 2
|
||||||
|
- Code(Zero) at (prev + 88, 20) to (start + 1, 31)
|
||||||
|
- Code(Zero) at (prev + 6, 21) to (start + 1, 18)
|
||||||
|
|
||||||
|
Function name: nested::closure_tail::{closure#0}::{closure#0}::{closure#0} (unused)
|
||||||
|
Raw bytes (9): 0x[01, 01, 00, 01, 00, 5a, 1c, 02, 1a]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 1
|
||||||
|
- Code(Zero) at (prev + 90, 28) to (start + 2, 26)
|
||||||
|
|
||||||
|
Function name: nested::outer_fn::middle_fn (unused)
|
||||||
|
Raw bytes (9): 0x[01, 01, 00, 01, 00, 11, 05, 05, 06]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 1
|
||||||
|
- Code(Zero) at (prev + 17, 5) to (start + 5, 6)
|
||||||
|
|
||||||
|
Function name: nested::outer_fn::middle_fn::inner_fn (unused)
|
||||||
|
Raw bytes (9): 0x[01, 01, 00, 01, 00, 12, 09, 02, 0a]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 1
|
||||||
|
- Code(Zero) at (prev + 18, 9) to (start + 2, 10)
|
||||||
|
|
111
tests/coverage/attr/nested.coverage
Normal file
111
tests/coverage/attr/nested.coverage
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
LL| |#![feature(coverage_attribute, stmt_expr_attributes)]
|
||||||
|
LL| |//@ edition: 2021
|
||||||
|
LL| |
|
||||||
|
LL| |// Demonstrates the interaction between #[coverage(off)] and various kinds of
|
||||||
|
LL| |// nested function.
|
||||||
|
LL| |
|
||||||
|
LL| |// FIXME(#126625): Coverage attributes should apply recursively to nested functions.
|
||||||
|
LL| |// FIXME(#126626): When an inner (non-closure) function has `#[coverage(off)]`,
|
||||||
|
LL| |// its lines can still be marked with misleading execution counts from its enclosing
|
||||||
|
LL| |// function.
|
||||||
|
LL| |
|
||||||
|
LL| |#[coverage(off)]
|
||||||
|
LL| |fn do_stuff() {}
|
||||||
|
LL| |
|
||||||
|
LL| |#[coverage(off)]
|
||||||
|
LL| |fn outer_fn() {
|
||||||
|
LL| 0| fn middle_fn() {
|
||||||
|
LL| 0| fn inner_fn() {
|
||||||
|
LL| 0| do_stuff();
|
||||||
|
LL| 0| }
|
||||||
|
LL| 0| do_stuff();
|
||||||
|
LL| 0| }
|
||||||
|
LL| | do_stuff();
|
||||||
|
LL| |}
|
||||||
|
LL| |
|
||||||
|
LL| |struct MyOuter;
|
||||||
|
LL| |impl MyOuter {
|
||||||
|
LL| | #[coverage(off)]
|
||||||
|
LL| | fn outer_method(&self) {
|
||||||
|
LL| | struct MyMiddle;
|
||||||
|
LL| | impl MyMiddle {
|
||||||
|
LL| 0| fn middle_method(&self) {
|
||||||
|
LL| 0| struct MyInner;
|
||||||
|
LL| 0| impl MyInner {
|
||||||
|
LL| 0| fn inner_method(&self) {
|
||||||
|
LL| 0| do_stuff();
|
||||||
|
LL| 0| }
|
||||||
|
LL| 0| }
|
||||||
|
LL| 0| do_stuff();
|
||||||
|
LL| 0| }
|
||||||
|
LL| | }
|
||||||
|
LL| | do_stuff();
|
||||||
|
LL| | }
|
||||||
|
LL| |}
|
||||||
|
LL| |
|
||||||
|
LL| |trait MyTrait {
|
||||||
|
LL| | fn trait_method(&self);
|
||||||
|
LL| |}
|
||||||
|
LL| |impl MyTrait for MyOuter {
|
||||||
|
LL| | #[coverage(off)]
|
||||||
|
LL| | fn trait_method(&self) {
|
||||||
|
LL| | struct MyMiddle;
|
||||||
|
LL| | impl MyTrait for MyMiddle {
|
||||||
|
LL| 0| fn trait_method(&self) {
|
||||||
|
LL| 0| struct MyInner;
|
||||||
|
LL| 0| impl MyTrait for MyInner {
|
||||||
|
LL| 0| fn trait_method(&self) {
|
||||||
|
LL| 0| do_stuff();
|
||||||
|
LL| 0| }
|
||||||
|
LL| 0| }
|
||||||
|
LL| 0| do_stuff();
|
||||||
|
LL| 0| }
|
||||||
|
LL| | }
|
||||||
|
LL| | do_stuff();
|
||||||
|
LL| | }
|
||||||
|
LL| |}
|
||||||
|
LL| |
|
||||||
|
LL| 1|fn closure_expr() {
|
||||||
|
LL| 1| let _outer = #[coverage(off)]
|
||||||
|
LL| | || {
|
||||||
|
LL| 0| let _middle = || {
|
||||||
|
LL| 0| let _inner = || {
|
||||||
|
LL| 0| do_stuff();
|
||||||
|
LL| 0| };
|
||||||
|
LL| 0| do_stuff();
|
||||||
|
LL| 0| };
|
||||||
|
LL| | do_stuff();
|
||||||
|
LL| | };
|
||||||
|
LL| 1| do_stuff();
|
||||||
|
LL| 1|}
|
||||||
|
LL| |
|
||||||
|
LL| |// This syntax is allowed, even without #![feature(stmt_expr_attributes)].
|
||||||
|
LL| 1|fn closure_tail() {
|
||||||
|
LL| 1| let _outer = {
|
||||||
|
LL| | #[coverage(off)]
|
||||||
|
LL| | || {
|
||||||
|
LL| | let _middle = {
|
||||||
|
LL| 0| || {
|
||||||
|
LL| 0| let _inner = {
|
||||||
|
LL| 0| || {
|
||||||
|
LL| 0| do_stuff();
|
||||||
|
LL| 0| }
|
||||||
|
LL| | };
|
||||||
|
LL| 0| do_stuff();
|
||||||
|
LL| 0| }
|
||||||
|
LL| | };
|
||||||
|
LL| | do_stuff();
|
||||||
|
LL| | }
|
||||||
|
LL| | };
|
||||||
|
LL| 1| do_stuff();
|
||||||
|
LL| 1|}
|
||||||
|
LL| |
|
||||||
|
LL| |#[coverage(off)]
|
||||||
|
LL| |fn main() {
|
||||||
|
LL| | outer_fn();
|
||||||
|
LL| | MyOuter.outer_method();
|
||||||
|
LL| | MyOuter.trait_method();
|
||||||
|
LL| | closure_expr();
|
||||||
|
LL| | closure_tail();
|
||||||
|
LL| |}
|
||||||
|
|
110
tests/coverage/attr/nested.rs
Normal file
110
tests/coverage/attr/nested.rs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#![feature(coverage_attribute, stmt_expr_attributes)]
|
||||||
|
//@ edition: 2021
|
||||||
|
|
||||||
|
// Demonstrates the interaction between #[coverage(off)] and various kinds of
|
||||||
|
// nested function.
|
||||||
|
|
||||||
|
// FIXME(#126625): Coverage attributes should apply recursively to nested functions.
|
||||||
|
// FIXME(#126626): When an inner (non-closure) function has `#[coverage(off)]`,
|
||||||
|
// its lines can still be marked with misleading execution counts from its enclosing
|
||||||
|
// function.
|
||||||
|
|
||||||
|
#[coverage(off)]
|
||||||
|
fn do_stuff() {}
|
||||||
|
|
||||||
|
#[coverage(off)]
|
||||||
|
fn outer_fn() {
|
||||||
|
fn middle_fn() {
|
||||||
|
fn inner_fn() {
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MyOuter;
|
||||||
|
impl MyOuter {
|
||||||
|
#[coverage(off)]
|
||||||
|
fn outer_method(&self) {
|
||||||
|
struct MyMiddle;
|
||||||
|
impl MyMiddle {
|
||||||
|
fn middle_method(&self) {
|
||||||
|
struct MyInner;
|
||||||
|
impl MyInner {
|
||||||
|
fn inner_method(&self) {
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait MyTrait {
|
||||||
|
fn trait_method(&self);
|
||||||
|
}
|
||||||
|
impl MyTrait for MyOuter {
|
||||||
|
#[coverage(off)]
|
||||||
|
fn trait_method(&self) {
|
||||||
|
struct MyMiddle;
|
||||||
|
impl MyTrait for MyMiddle {
|
||||||
|
fn trait_method(&self) {
|
||||||
|
struct MyInner;
|
||||||
|
impl MyTrait for MyInner {
|
||||||
|
fn trait_method(&self) {
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn closure_expr() {
|
||||||
|
let _outer = #[coverage(off)]
|
||||||
|
|| {
|
||||||
|
let _middle = || {
|
||||||
|
let _inner = || {
|
||||||
|
do_stuff();
|
||||||
|
};
|
||||||
|
do_stuff();
|
||||||
|
};
|
||||||
|
do_stuff();
|
||||||
|
};
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This syntax is allowed, even without #![feature(stmt_expr_attributes)].
|
||||||
|
fn closure_tail() {
|
||||||
|
let _outer = {
|
||||||
|
#[coverage(off)]
|
||||||
|
|| {
|
||||||
|
let _middle = {
|
||||||
|
|| {
|
||||||
|
let _inner = {
|
||||||
|
|| {
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[coverage(off)]
|
||||||
|
fn main() {
|
||||||
|
outer_fn();
|
||||||
|
MyOuter.outer_method();
|
||||||
|
MyOuter.trait_method();
|
||||||
|
closure_expr();
|
||||||
|
closure_tail();
|
||||||
|
}
|
32
tests/coverage/attr/off-on-sandwich.cov-map
Normal file
32
tests/coverage/attr/off-on-sandwich.cov-map
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
Function name: off_on_sandwich::dense_a::dense_b
|
||||||
|
Raw bytes (9): 0x[01, 01, 00, 01, 01, 14, 05, 07, 06]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 1
|
||||||
|
- Code(Counter(0)) at (prev + 20, 5) to (start + 7, 6)
|
||||||
|
|
||||||
|
Function name: off_on_sandwich::sparse_a::sparse_b
|
||||||
|
Raw bytes (9): 0x[01, 01, 00, 01, 01, 22, 05, 10, 06]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 1
|
||||||
|
- Code(Counter(0)) at (prev + 34, 5) to (start + 16, 6)
|
||||||
|
|
||||||
|
Function name: off_on_sandwich::sparse_a::sparse_b::sparse_c
|
||||||
|
Raw bytes (9): 0x[01, 01, 00, 01, 01, 26, 09, 0b, 0a]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 1
|
||||||
|
- Code(Counter(0)) at (prev + 38, 9) to (start + 11, 10)
|
||||||
|
|
||||||
|
Function name: off_on_sandwich::sparse_a::sparse_b::sparse_c::sparse_d
|
||||||
|
Raw bytes (9): 0x[01, 01, 00, 01, 01, 29, 0d, 07, 0e]
|
||||||
|
Number of files: 1
|
||||||
|
- file 0 => global file 1
|
||||||
|
Number of expressions: 0
|
||||||
|
Number of file 0 mappings: 1
|
||||||
|
- Code(Counter(0)) at (prev + 41, 13) to (start + 7, 14)
|
||||||
|
|
58
tests/coverage/attr/off-on-sandwich.coverage
Normal file
58
tests/coverage/attr/off-on-sandwich.coverage
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
LL| |#![feature(coverage_attribute)]
|
||||||
|
LL| |//@ edition: 2021
|
||||||
|
LL| |
|
||||||
|
LL| |// Demonstrates the interaction of `#[coverage(off)]` and `#[coverage(on)]`
|
||||||
|
LL| |// in nested functions.
|
||||||
|
LL| |
|
||||||
|
LL| |// FIXME(#126625): Coverage attributes should apply recursively to nested functions.
|
||||||
|
LL| |// FIXME(#126626): When an inner (non-closure) function has `#[coverage(off)]`,
|
||||||
|
LL| |// its lines can still be marked with misleading execution counts from its enclosing
|
||||||
|
LL| |// function.
|
||||||
|
LL| |
|
||||||
|
LL| |#[coverage(off)]
|
||||||
|
LL| |fn do_stuff() {}
|
||||||
|
LL| |
|
||||||
|
LL| |#[coverage(off)]
|
||||||
|
LL| |fn dense_a() {
|
||||||
|
LL| | dense_b();
|
||||||
|
LL| | dense_b();
|
||||||
|
LL| | #[coverage(on)]
|
||||||
|
LL| 2| fn dense_b() {
|
||||||
|
LL| 2| dense_c();
|
||||||
|
LL| 2| dense_c();
|
||||||
|
LL| 2| #[coverage(off)]
|
||||||
|
LL| 2| fn dense_c() {
|
||||||
|
LL| 2| do_stuff();
|
||||||
|
LL| 2| }
|
||||||
|
LL| 2| }
|
||||||
|
LL| |}
|
||||||
|
LL| |
|
||||||
|
LL| |#[coverage(off)]
|
||||||
|
LL| |fn sparse_a() {
|
||||||
|
LL| | sparse_b();
|
||||||
|
LL| | sparse_b();
|
||||||
|
LL| 2| fn sparse_b() {
|
||||||
|
LL| 2| sparse_c();
|
||||||
|
LL| 2| sparse_c();
|
||||||
|
LL| 2| #[coverage(on)]
|
||||||
|
LL| 4| fn sparse_c() {
|
||||||
|
LL| 4| sparse_d();
|
||||||
|
LL| 4| sparse_d();
|
||||||
|
LL| 8| fn sparse_d() {
|
||||||
|
LL| 8| sparse_e();
|
||||||
|
LL| 8| sparse_e();
|
||||||
|
LL| 8| #[coverage(off)]
|
||||||
|
LL| 8| fn sparse_e() {
|
||||||
|
LL| 8| do_stuff();
|
||||||
|
LL| 8| }
|
||||||
|
LL| 8| }
|
||||||
|
LL| 4| }
|
||||||
|
LL| 2| }
|
||||||
|
LL| |}
|
||||||
|
LL| |
|
||||||
|
LL| |#[coverage(off)]
|
||||||
|
LL| |fn main() {
|
||||||
|
LL| | dense_a();
|
||||||
|
LL| | sparse_a();
|
||||||
|
LL| |}
|
||||||
|
|
57
tests/coverage/attr/off-on-sandwich.rs
Normal file
57
tests/coverage/attr/off-on-sandwich.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#![feature(coverage_attribute)]
|
||||||
|
//@ edition: 2021
|
||||||
|
|
||||||
|
// Demonstrates the interaction of `#[coverage(off)]` and `#[coverage(on)]`
|
||||||
|
// in nested functions.
|
||||||
|
|
||||||
|
// FIXME(#126625): Coverage attributes should apply recursively to nested functions.
|
||||||
|
// FIXME(#126626): When an inner (non-closure) function has `#[coverage(off)]`,
|
||||||
|
// its lines can still be marked with misleading execution counts from its enclosing
|
||||||
|
// function.
|
||||||
|
|
||||||
|
#[coverage(off)]
|
||||||
|
fn do_stuff() {}
|
||||||
|
|
||||||
|
#[coverage(off)]
|
||||||
|
fn dense_a() {
|
||||||
|
dense_b();
|
||||||
|
dense_b();
|
||||||
|
#[coverage(on)]
|
||||||
|
fn dense_b() {
|
||||||
|
dense_c();
|
||||||
|
dense_c();
|
||||||
|
#[coverage(off)]
|
||||||
|
fn dense_c() {
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[coverage(off)]
|
||||||
|
fn sparse_a() {
|
||||||
|
sparse_b();
|
||||||
|
sparse_b();
|
||||||
|
fn sparse_b() {
|
||||||
|
sparse_c();
|
||||||
|
sparse_c();
|
||||||
|
#[coverage(on)]
|
||||||
|
fn sparse_c() {
|
||||||
|
sparse_d();
|
||||||
|
sparse_d();
|
||||||
|
fn sparse_d() {
|
||||||
|
sparse_e();
|
||||||
|
sparse_e();
|
||||||
|
#[coverage(off)]
|
||||||
|
fn sparse_e() {
|
||||||
|
do_stuff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[coverage(off)]
|
||||||
|
fn main() {
|
||||||
|
dense_a();
|
||||||
|
sparse_a();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user