1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-09-16 19:56:17 +00:00

Merge branch 'main' into mkdir-fix

This commit is contained in:
Sylvestre Ledru 2022-03-15 12:03:47 +01:00 committed by GitHub
commit 9dea9b4b83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
246 changed files with 2840 additions and 942 deletions

View file

@ -334,19 +334,20 @@ fn test_chmod_recursive() {
make_file(&at.plus_as_string("a/b/c/c"), 0o100444);
make_file(&at.plus_as_string("z/y"), 0o100444);
// only the permissions of folder `a` and `z` are changed
// folder can't be read after read permission is removed
ucmd.arg("-R")
.arg("--verbose")
.arg("-r,a+w")
.arg("a")
.arg("z")
.succeeds()
.stdout_contains(&"to 0333 (-wx-wx-wx)")
.stdout_contains(&"to 0222 (-w--w--w-)");
.fails()
.stderr_is("chmod: Permission denied");
assert_eq!(at.metadata("z/y").permissions().mode(), 0o100222);
assert_eq!(at.metadata("a/a").permissions().mode(), 0o100222);
assert_eq!(at.metadata("a/b/b").permissions().mode(), 0o100222);
assert_eq!(at.metadata("a/b/c/c").permissions().mode(), 0o100222);
assert_eq!(at.metadata("z/y").permissions().mode(), 0o100444);
assert_eq!(at.metadata("a/a").permissions().mode(), 0o100444);
assert_eq!(at.metadata("a/b/b").permissions().mode(), 0o100444);
assert_eq!(at.metadata("a/b/c/c").permissions().mode(), 0o100444);
println!("mode {:o}", at.metadata("a").permissions().mode());
assert_eq!(at.metadata("a").permissions().mode(), 0o40333);
assert_eq!(at.metadata("z").permissions().mode(), 0o40333);
@ -356,6 +357,23 @@ fn test_chmod_recursive() {
}
}
#[test]
#[allow(clippy::unreadable_literal)]
fn test_chmod_recursive_read_permission() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("a");
at.mkdir("a/b");
let mut perms = at.metadata("a/b").permissions();
perms.set_mode(0o311);
set_permissions(at.plus_as_string("a/b"), perms.clone()).unwrap();
set_permissions(at.plus_as_string("a"), perms).unwrap();
ucmd.arg("-R").arg("u+r").arg("a").succeeds();
assert_eq!(at.metadata("a").permissions().mode(), 0o40711);
assert_eq!(at.metadata("a/b").permissions().mode(), 0o40711);
}
#[test]
fn test_chmod_non_existing_file() {
new_ucmd!()
@ -455,8 +473,8 @@ fn test_chmod_symlink_non_existing_file_recursive() {
let expected_stdout = &format!(
// spell-checker:disable-next-line
"mode of '{}' retained as 0755 (rwxr-xr-x)\nneither symbolic link '{}/{}' nor referent has been changed",
test_directory, test_directory, test_symlink
"mode of '{}' retained as 0755 (rwxr-xr-x)",
test_directory
);
// '-v': this should succeed without stderr

View file

@ -29,6 +29,7 @@ static TEST_EXISTING_FILE: &str = "existing_file.txt";
static TEST_HELLO_WORLD_SOURCE: &str = "hello_world.txt";
static TEST_HELLO_WORLD_SOURCE_SYMLINK: &str = "hello_world.txt.link";
static TEST_HELLO_WORLD_DEST: &str = "copy_of_hello_world.txt";
static TEST_HELLO_WORLD_DEST_SYMLINK: &str = "copy_of_hello_world.txt.link";
static TEST_HOW_ARE_YOU_SOURCE: &str = "how_are_you.txt";
static TEST_HOW_ARE_YOU_DEST: &str = "hello_dir/how_are_you.txt";
static TEST_COPY_TO_FOLDER: &str = "hello_dir/";
@ -688,6 +689,51 @@ fn test_cp_no_deref() {
assert_eq!(at.read(path_to_check), "Hello, World!\n");
}
#[test]
fn test_cp_no_deref_link_onto_link() {
let (at, mut ucmd) = at_and_ucmd!();
at.copy(TEST_HELLO_WORLD_SOURCE, TEST_HELLO_WORLD_DEST);
#[cfg(not(windows))]
let _r = fs::symlink(
TEST_HELLO_WORLD_SOURCE,
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
);
#[cfg(windows)]
let _r = symlink_file(
TEST_HELLO_WORLD_SOURCE,
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
);
#[cfg(not(windows))]
let _r = fs::symlink(
TEST_HELLO_WORLD_DEST,
at.subdir.join(TEST_HELLO_WORLD_DEST_SYMLINK),
);
#[cfg(windows)]
let _r = symlink_file(
TEST_HELLO_WORLD_DEST,
at.subdir.join(TEST_HELLO_WORLD_DEST_SYMLINK),
);
ucmd.arg("-P")
.arg(TEST_HELLO_WORLD_SOURCE_SYMLINK)
.arg(TEST_HELLO_WORLD_DEST_SYMLINK)
.succeeds();
// Ensure that the target of the destination was not modified.
assert!(!at
.symlink_metadata(TEST_HELLO_WORLD_DEST)
.file_type()
.is_symlink());
assert!(at
.symlink_metadata(TEST_HELLO_WORLD_DEST_SYMLINK)
.file_type()
.is_symlink());
assert_eq!(at.read(TEST_HELLO_WORLD_DEST_SYMLINK), "Hello, World!\n");
}
#[test]
fn test_cp_strip_trailing_slashes() {
let (at, mut ucmd) = at_and_ucmd!();
@ -1463,6 +1509,20 @@ fn test_cp_archive_on_nonexistent_file() {
);
}
#[test]
fn test_cp_link_backup() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file2");
ucmd.arg("-l")
.arg("-b")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg("file2")
.succeeds();
assert!(at.file_exists("file2~"));
assert_eq!(at.read("file2"), "Hello, World!\n");
}
#[test]
#[cfg(unix)]
fn test_cp_fifo() {

View file

@ -1,3 +1,4 @@
// spell-checker:ignore udev
use crate::common::util::*;
#[test]
@ -27,6 +28,8 @@ fn test_df_compatible_si() {
#[test]
fn test_df_output() {
// TODO These should fail because `-total` should have two dashes,
// not just one. But they don't fail.
if cfg!(target_os = "macos") {
new_ucmd!().arg("-H").arg("-total").succeeds().
stdout_only("Filesystem Size Used Available Capacity Use% Mounted on \n");
@ -40,24 +43,29 @@ fn test_df_output() {
/// Test that the order of rows in the table does not change across executions.
#[test]
fn test_order_same() {
// TODO When #3057 is resolved, we should just use
//
// new_ucmd!().arg("--output=source").succeeds().stdout_move_str();
//
// instead of parsing the entire `df` table as a string.
let output1 = new_ucmd!().succeeds().stdout_move_str();
let output2 = new_ucmd!().succeeds().stdout_move_str();
let output1 = new_ucmd!()
.arg("--output=source")
.succeeds()
.stdout_move_str();
let output2 = new_ucmd!()
.arg("--output=source")
.succeeds()
.stdout_move_str();
assert_eq!(output1, output2);
}
/// Test of mount point begin repeated
#[cfg(unix)]
#[test]
fn test_output_mp_repeat() {
let output1 = new_ucmd!().arg("/").arg("/").succeeds().stdout_move_str();
let output1: Vec<String> = output1
.lines()
.map(|l| String::from(l.split_once(' ').unwrap().0))
.collect();
let output2: Vec<String> = output2
.lines()
.map(|l| String::from(l.split_once(' ').unwrap().0))
.collect();
assert_eq!(output1, output2);
assert_eq!(3, output1.len());
assert_eq!(output1[1], output1[2]);
}
#[test]
fn test_output_conflict_options() {
for option in ["-i", "-T", "-P"] {
@ -77,4 +85,136 @@ fn test_type_option() {
new_ucmd!().args(&["-t", "ext4", "-t", "ext3"]).succeeds();
}
#[test]
fn test_total() {
// Example output:
//
// Filesystem 1K-blocks Used Available Use% Mounted on
// udev 3858016 0 3858016 0% /dev
// ...
// /dev/loop14 63488 63488 0 100% /snap/core20/1361
// total 258775268 98099712 148220200 40% -
let output = new_ucmd!().arg("--total").succeeds().stdout_move_str();
// Skip the header line.
let lines: Vec<&str> = output.lines().skip(1).collect();
// Parse the values from the last row.
let last_line = lines.last().unwrap();
let mut iter = last_line.split_whitespace();
assert_eq!(iter.next().unwrap(), "total");
let reported_total_size = iter.next().unwrap().parse().unwrap();
let reported_total_used = iter.next().unwrap().parse().unwrap();
let reported_total_avail = iter.next().unwrap().parse().unwrap();
// Loop over each row except the last, computing the sum of each column.
let mut computed_total_size = 0;
let mut computed_total_used = 0;
let mut computed_total_avail = 0;
let n = lines.len();
for line in &lines[..n - 1] {
let mut iter = line.split_whitespace();
iter.next().unwrap();
computed_total_size += iter.next().unwrap().parse::<u64>().unwrap();
computed_total_used += iter.next().unwrap().parse::<u64>().unwrap();
computed_total_avail += iter.next().unwrap().parse::<u64>().unwrap();
}
// Check that the sum of each column matches the reported value in
// the last row.
assert_eq!(computed_total_size, reported_total_size);
assert_eq!(computed_total_used, reported_total_used);
assert_eq!(computed_total_avail, reported_total_avail);
}
#[test]
fn test_use_percentage() {
// Example output:
//
// Filesystem 1K-blocks Used Available Use% Mounted on
// udev 3858016 0 3858016 0% /dev
// ...
// /dev/loop14 63488 63488 0 100% /snap/core20/1361
let output = new_ucmd!().succeeds().stdout_move_str();
// Skip the header line.
let lines: Vec<&str> = output.lines().skip(1).collect();
for line in lines {
let mut iter = line.split_whitespace();
iter.next();
let reported_size = iter.next().unwrap().parse::<f64>().unwrap();
let reported_used = iter.next().unwrap().parse::<f64>().unwrap();
// Skip "Available" column
iter.next();
if cfg!(target_os = "macos") {
// Skip "Capacity" column
iter.next();
}
let reported_percentage = iter.next().unwrap();
let reported_percentage = reported_percentage[..reported_percentage.len() - 1]
.parse::<u8>()
.unwrap();
let computed_percentage = (100.0 * (reported_used / reported_size)).ceil() as u8;
assert_eq!(computed_percentage, reported_percentage);
}
}
#[test]
fn test_block_size_1024() {
fn get_header(block_size: u64) -> String {
// TODO When #3057 is resolved, we should just use
//
// new_ucmd!().arg("--output=size").succeeds().stdout_move_str();
//
// instead of parsing the entire `df` table as a string.
let output = new_ucmd!()
.args(&["-B", &format!("{}", block_size)])
.succeeds()
.stdout_move_str();
let first_line = output.lines().next().unwrap();
let mut column_labels = first_line.split_whitespace();
let size_column_label = column_labels.nth(1).unwrap();
size_column_label.into()
}
assert_eq!(get_header(1024), "1K-blocks");
assert_eq!(get_header(2048), "2K-blocks");
assert_eq!(get_header(4096), "4K-blocks");
assert_eq!(get_header(1024 * 1024), "1M-blocks");
assert_eq!(get_header(2 * 1024 * 1024), "2M-blocks");
assert_eq!(get_header(1024 * 1024 * 1024), "1G-blocks");
assert_eq!(get_header(34 * 1024 * 1024 * 1024), "34G-blocks");
}
// TODO The spacing does not match GNU df. Also we need to remove
// trailing spaces from the heading row.
#[test]
fn test_output_selects_columns() {
let output = new_ucmd!()
.args(&["--output=source"])
.succeeds()
.stdout_move_str();
assert_eq!(output.lines().next().unwrap(), "Filesystem ");
let output = new_ucmd!()
.args(&["--output=source,target"])
.succeeds()
.stdout_move_str();
assert_eq!(
output.lines().next().unwrap(),
"Filesystem Mounted on "
);
let output = new_ucmd!()
.args(&["--output=source,target,used"])
.succeeds()
.stdout_move_str();
assert_eq!(
output.lines().next().unwrap(),
"Filesystem Mounted on Used "
);
}
// ToDO: more tests...

View file

@ -11,7 +11,9 @@ static TEST_DIR4: &str = "mkdir_test4/mkdir_test4_1";
static TEST_DIR5: &str = "mkdir_test5/mkdir_test5_1";
static TEST_DIR6: &str = "mkdir_test6";
static TEST_FILE7: &str = "mkdir_test7";
static TEST_DIR8: &str = "mkdir_test8";
static TEST_DIR8: &str = "mkdir_test8/mkdir_test8_1/mkdir_test8_2";
static TEST_DIR9: &str = "mkdir_test9/../mkdir_test9_1/../mkdir_test9_2";
static TEST_DIR10: &str = "mkdir_test10";
#[test]
fn test_mkdir_mkdir() {
@ -104,6 +106,27 @@ fn test_multi_symbolic() {
assert_eq!(perms, 0o40750);
}
#[test]
fn test_recursive_reporting() {
new_ucmd!()
.arg("-p")
.arg("-v")
.arg(TEST_DIR8)
.succeeds()
.stdout_contains("created directory 'mkdir_test8'")
.stdout_contains("created directory 'mkdir_test8/mkdir_test8_1'")
.stdout_contains("created directory 'mkdir_test8/mkdir_test8_1/mkdir_test8_2'");
new_ucmd!().arg("-v").arg(TEST_DIR8).fails().no_stdout();
new_ucmd!()
.arg("-p")
.arg("-v")
.arg(TEST_DIR9)
.succeeds()
.stdout_contains("created directory 'mkdir_test9'")
.stdout_contains("created directory 'mkdir_test9/../mkdir_test9_1'")
.stdout_contains("created directory 'mkdir_test9/../mkdir_test9_1/../mkdir_test9_2'");
}
#[test]
#[cfg(not(windows))]
fn test_umask_compliance() {
@ -112,8 +135,8 @@ fn test_umask_compliance() {
let original_umask = unsafe { umask(umask_set) };
ucmd.arg(TEST_DIR8).succeeds();
let perms = at.metadata(TEST_DIR8).permissions().mode() as mode_t;
ucmd.arg(TEST_DIR10).succeeds();
let perms = at.metadata(TEST_DIR10).permissions().mode() as mode_t;
assert_eq!(perms, (!umask_set & 0o0777) + 0o40000); // before compare, add the setguid, uid bits
unsafe { umask(original_umask); } // set umask back to original
@ -122,5 +145,4 @@ fn test_umask_compliance() {
for i in 0o0..0o777 { // tests all permission combinations
test_single_case(i);
}
}

View file

@ -1,3 +1,4 @@
// spell-checker:ignore incorrectnumber
use crate::common::util::*;
#[test]
@ -15,11 +16,11 @@ fn test_nproc_all_omp() {
let result = TestScenario::new(util_name!())
.ucmd_keepenv()
.env("OMP_NUM_THREADS", "1")
.env("OMP_NUM_THREADS", "60")
.succeeds();
let nproc_omp: u8 = result.stdout_str().trim().parse().unwrap();
assert!(nproc - 1 == nproc_omp);
assert!(nproc_omp == 60);
let result = TestScenario::new(util_name!())
.ucmd_keepenv()
@ -28,20 +29,46 @@ fn test_nproc_all_omp() {
.succeeds();
let nproc_omp: u8 = result.stdout_str().trim().parse().unwrap();
assert!(nproc == nproc_omp);
// If the parsing fails, returns the number of CPU
let result = TestScenario::new(util_name!())
.ucmd_keepenv()
.env("OMP_NUM_THREADS", "incorrectnumber") // returns the number CPU
.succeeds();
let nproc_omp: u8 = result.stdout_str().trim().parse().unwrap();
assert!(nproc == nproc_omp);
}
#[test]
fn test_nproc_ignore() {
let result = new_ucmd!().succeeds();
let nproc: u8 = result.stdout_str().trim().parse().unwrap();
if nproc > 1 {
let nproc_total: u8 = result.stdout_str().trim().parse().unwrap();
if nproc_total > 1 {
// Ignore all CPU but one
let result = TestScenario::new(util_name!())
.ucmd_keepenv()
.arg("--ignore")
.arg((nproc - 1).to_string())
.arg((nproc_total - 1).to_string())
.succeeds();
let nproc: u8 = result.stdout_str().trim().parse().unwrap();
assert!(nproc == 1);
// Ignore all CPU but one with a string
let result = TestScenario::new(util_name!())
.ucmd_keepenv()
.arg("--ignore= 1")
.succeeds();
let nproc: u8 = result.stdout_str().trim().parse().unwrap();
assert!(nproc_total - 1 == nproc);
}
}
#[test]
fn test_nproc_ignore_all_omp() {
let result = TestScenario::new(util_name!())
.ucmd_keepenv()
.env("OMP_NUM_THREADS", "42")
.arg("--ignore=40")
.succeeds();
let nproc: u8 = result.stdout_str().trim().parse().unwrap();
assert!(nproc == 2);
}

View file

@ -448,3 +448,16 @@ fn test_with_join_lines_option() {
&valid_last_modified_template_vars(start),
);
}
#[test]
fn test_value_for_number_lines() {
// *5 is of the form [SEP[NUMBER]] so is accepted and succeeds
new_ucmd!().args(&["-n", "*5", "test.log"]).succeeds();
// a is of the form [SEP[NUMBER]] so is accepted and succeeds
new_ucmd!().args(&["-n", "a", "test.log"]).succeeds();
// foo5.txt is of not the form [SEP[NUMBER]] so is not used as value.
// Therefore, pr tries to access the file, which does not exist.
new_ucmd!().args(&["-n", "foo5.txt", "test.log"]).fails();
}

View file

@ -228,6 +228,22 @@ fn sub_num_float() {
.stdout_only("twenty is 20.000000");
}
#[test]
fn sub_num_float_e_round() {
new_ucmd!()
.args(&["%e", "99999999"])
.succeeds()
.stdout_only("1.000000e+08");
}
#[test]
fn sub_num_float_e_no_round() {
new_ucmd!()
.args(&["%e", "99999994"])
.succeeds()
.stdout_only("9.999999e+07");
}
#[test]
fn sub_num_float_round() {
new_ucmd!()
@ -236,6 +252,14 @@ fn sub_num_float_round() {
.stdout_only("two is 2.000000");
}
#[test]
fn sub_num_float_round_nines_dec() {
new_ucmd!()
.args(&["%f", "0.99999999"])
.succeeds()
.stdout_only("1.000000");
}
#[test]
fn sub_num_sci_lower() {
new_ucmd!()

View file

@ -338,24 +338,20 @@ fn test_dictionary_order() {
#[test]
fn test_dictionary_order2() {
for non_dictionary_order2_param in &["-d"] {
new_ucmd!()
.pipe_in("a👦🏻aa\tb\naaaa\tb") // spell-checker:disable-line
.arg(non_dictionary_order2_param) // spell-checker:disable-line
.succeeds()
.stdout_only("a👦🏻aa\tb\naaaa\tb\n"); // spell-checker:disable-line
}
new_ucmd!()
.pipe_in("a👦🏻aa\tb\naaaa\tb") // spell-checker:disable-line
.arg("-d")
.succeeds()
.stdout_only("a👦🏻aa\tb\naaaa\tb\n"); // spell-checker:disable-line
}
#[test]
fn test_non_printing_chars() {
for non_printing_chars_param in &["-i"] {
new_ucmd!()
.pipe_in("a👦🏻aa\naaaa") // spell-checker:disable-line
.arg(non_printing_chars_param) // spell-checker:disable-line
.succeeds()
.stdout_only("a👦🏻aa\naaaa\n"); // spell-checker:disable-line
}
new_ucmd!()
.pipe_in("a👦🏻aa\naaaa") // spell-checker:disable-line
.arg("-i")
.succeeds()
.stdout_only("a👦🏻aa\naaaa\n"); // spell-checker:disable-line
}
#[test]
@ -486,14 +482,12 @@ fn test_default_unsorted_ints2() {
#[test]
fn test_numeric_unique_ints2() {
for numeric_unique_sort_param in &["-nu"] {
let input = "9\n9\n8\n1\n";
new_ucmd!()
.arg(numeric_unique_sort_param)
.pipe_in(input)
.succeeds()
.stdout_only("1\n8\n9\n");
}
let input = "9\n9\n8\n1\n";
new_ucmd!()
.arg("-nu")
.pipe_in(input)
.succeeds()
.stdout_only("1\n8\n9\n");
}
#[test]

View file

@ -2,7 +2,7 @@
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes
// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes onehundredlines nbbbb
extern crate rand;
extern crate regex;
@ -587,3 +587,21 @@ fn test_lines() {
assert_eq!(file_read("xaa"), "1\n2\n3\n");
assert_eq!(file_read("xab"), "4\n5\n");
}
#[test]
fn test_lines_kth() {
new_ucmd!()
.args(&["-n", "l/3/10", "onehundredlines.txt"])
.succeeds()
.stdout_only("20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n");
}
#[test]
fn test_line_bytes() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.args(&["-C", "8", "letters.txt"]).succeeds();
assert_eq!(at.read("xaa"), "aaaaaaaa");
assert_eq!(at.read("xab"), "a\nbbbb\n");
assert_eq!(at.read("xac"), "cccc\ndd\n");
assert_eq!(at.read("xad"), "ee\n");
}

View file

@ -45,3 +45,22 @@ fn test_zero_timeout() {
.no_stderr()
.no_stdout();
}
#[test]
fn test_command_empty_args() {
new_ucmd!()
.args(&["", ""])
.fails()
.stderr_contains("timeout: empty string");
}
#[test]
fn test_preserve_status() {
new_ucmd!()
.args(&["--preserve-status", ".1", "sleep", "10"])
.fails()
// 128 + SIGTERM = 128 + 15
.code_is(128 + 15)
.no_stderr()
.no_stdout();
}

View file

@ -510,6 +510,27 @@ fn test_touch_no_such_file_error_msg() {
));
}
#[test]
fn test_touch_changes_time_of_file_in_stdout() {
// command like: `touch - 1< ./c`
// should change the timestamp of c
let (at, mut ucmd) = at_and_ucmd!();
let file = "test_touch_changes_time_of_file_in_stdout";
at.touch(file);
assert!(at.file_exists(file));
let (_, mtime) = get_file_times(&at, file);
ucmd.args(&["-"])
.set_stdout(at.make_file(file))
.succeeds()
.no_stderr();
let (_, mtime_after) = get_file_times(&at, file);
assert!(mtime_after != mtime);
}
#[test]
#[cfg(unix)]
fn test_touch_permission_denied_error_msg() {

View file

@ -69,10 +69,8 @@ fn test_login() {
#[test]
fn test_m() {
let ts = TestScenario::new(util_name!());
for opt in &["-m"] {
let expected_stdout = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str();
ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout);
}
let expected_stdout = unwrap_or_return!(expected_result(&ts, &["-m"])).stdout_move_str();
ts.ucmd().arg("-m").succeeds().stdout_is(expected_stdout);
}
#[cfg(unix)]

5
tests/fixtures/split/letters.txt vendored Normal file
View file

@ -0,0 +1,5 @@
aaaaaaaaa
bbbb
cccc
dd
ee

100
tests/fixtures/split/onehundredlines.txt vendored Normal file
View file

@ -0,0 +1,100 @@
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99