浏览代码

Update master from bsys-ws17-template/hw6

Manuel Vögele 8 年前
父节点
当前提交
6acf783a10

+ 19
- 0
files/hw6.txt 查看文件

@@ -0,0 +1,19 @@
1
+./hw6/README.md
2
+
3
+./hw6/task1/Cargo.lock
4
+./hw6/task1/Cargo.toml
5
+./hw6/task1/src/main.rs
6
+./hw6/task1/tests/output.bats
7
+
8
+./hw6/task2/Cargo.lock
9
+./hw6/task2/Cargo.toml
10
+./hw6/task2/src/main.rs
11
+./hw6/task2/src/shell.rs
12
+./hw6/task2/src/command.rs
13
+./hw6/task2/src/unit_tests_shell.rs
14
+
15
+./hw6/simu1/ANSWERS.md
16
+./hw6/simu1/QUESTIONS.md
17
+./hw6/simu1/README-lottery.md
18
+./hw6/simu1/lottery.py
19
+

+ 27
- 0
hw6/README.md 查看文件

@@ -0,0 +1,27 @@
1
+# hw6
2
+
3
+## Tasks
4
+
5
+To fulfill **hw6** you have to solve:
6
+
7
+- task1
8
+- task2
9
+- simu1
10
+
11
+## Files
12
+
13
+You find already or reuse some files for rust tasks. Please remember to use cargo to create a new project for each task and copy the given files into their dedicated directories.
14
+
15
+## Pull-Request
16
+
17
+Please merge any accepted reviews into your branch. If you are ready with the homework, all tests run, please create a pull request named **hw6**.
18
+
19
+## Credits for hw6
20
+
21
+| Task     | max. Credits | Comment |
22
+| -------- | ------------ | ------- |
23
+| task1    | 1            |         |
24
+| task2    | 2            |         |
25
+| simu1    | 1            |         |
26
+| Deadline | +1           |         |
27
+| =        | 5            |         |

+ 26
- 0
hw6/simu1/QUESTIONS.md 查看文件

@@ -0,0 +1,26 @@
1
+# QUESTIONS: 9-Scheduling-Lottery
2
+
3
+This program, **lottery.py**, allows you to see how a lottery scheduler works.
4
+See the README for details.
5
+
6
+## Warmup
7
+
8
+1. Compute the solutions for simulations with 3 jobs and random seeds of 1, 2,
9
+   and 3.
10
+
11
+## Questions
12
+
13
+1. Now run with two specific jobs: each of length 10, but one (job 0) with just
14
+   1 ticket and the other (job 1) with 100 (e.g., `-l 10:1,10:100`). 
15
+   - What happens when the number of tickets is so imbalanced? 
16
+   - Will job 0 ever run before job 1 completes? How often? 
17
+   - In general, what does such a ticket imbalance do to the behavior of lottery
18
+     scheduling?
19
+1. When running with two jobs of length 100 and equal ticket allocations of 100
20
+   (`-l 100:100,100:100`), how unfair is the scheduler? Run with some different
21
+   random seeds to determine the (probabilistic) answer; let unfairness be
22
+   determined by how much earlier one job finishes than the other.
23
+1. How does your answer to the previous question change as the quantum size
24
+   (`-q`) gets larger?
25
+1. Can you make a version of the graph that is found in the chapter? What else
26
+   would be worth exploring? How would the graph look with a stride scheduler?

+ 126
- 0
hw6/simu1/README-lottery.md 查看文件

@@ -0,0 +1,126 @@
1
+# README Scheduler: Lottery
2
+
3
+This program, lottery.py, allows you to see how a lottery scheduler works. As
4
+always, there are two steps to running the program. First, run without the -c
5
+flag: this shows you what problem to solve without revealing the answers. 
6
+
7
+```text
8
+prompt> ./lottery.py -j 2 -s 0
9
+...
10
+Here is the job list, with the run time of each job: 
11
+  Job 0 ( length = 8, tickets = 75 )
12
+  Job 1 ( length = 4, tickets = 25 )
13
+
14
+Here is the set of random numbers you will need (at most):
15
+Random 511275
16
+Random 404934
17
+Random 783799
18
+Random 303313
19
+Random 476597
20
+Random 583382
21
+Random 908113
22
+Random 504687
23
+Random 281838
24
+Random 755804
25
+Random 618369
26
+Random 250506
27
+```
28
+
29
+When you run the simulator in this manner, it first assigns you some random jobs
30
+(here of lengths 8, and 4), each with some number of tickets (here 75 and 25,
31
+respectively). The simulator also gives you a list of random numbers, which you
32
+will need to determine what the lottery scheduler will do. The random numbers
33
+are chosen to be between 0 and a large number; thus, you'll have to use the
34
+modulo operator to compute the lottery winner (i.e., winner should equal this
35
+random number modulo the total number of tickets in the system). 
36
+
37
+Running with -c shows exactly what you are supposed to calculate:
38
+
39
+```text
40
+prompt> ./lottery.py -j 2 -s 0 -c
41
+...
42
+** Solutions **
43
+Random 511275 -> Winning ticket 75 (of 100) -> Run 1
44
+  Jobs:  (  job:0 timeleft:8 tix:75 ) (* job:1 timeleft:4 tix:25 )
45
+Random 404934 -> Winning ticket 34 (of 100) -> Run 0
46
+  Jobs:  (* job:0 timeleft:8 tix:75 ) (  job:1 timeleft:3 tix:25 )
47
+Random 783799 -> Winning ticket 99 (of 100) -> Run 1
48
+  Jobs:  (  job:0 timeleft:7 tix:75 ) (* job:1 timeleft:3 tix:25 )
49
+Random 303313 -> Winning ticket 13 (of 100) -> Run 0
50
+  Jobs:  (* job:0 timeleft:7 tix:75 ) (  job:1 timeleft:2 tix:25 )
51
+Random 476597 -> Winning ticket 97 (of 100) -> Run 1
52
+  Jobs:  (  job:0 timeleft:6 tix:75 ) (* job:1 timeleft:2 tix:25 )
53
+Random 583382 -> Winning ticket 82 (of 100) -> Run 1
54
+  Jobs:  (  job:0 timeleft:6 tix:75 ) (* job:1 timeleft:1 tix:25 )
55
+--> JOB 1 DONE at time 6
56
+Random 908113 -> Winning ticket 13 (of 75) -> Run 0
57
+  Jobs:  (* job:0 timeleft:6 tix:75 ) (  job:1 timeleft:0 tix:--- )
58
+Random 504687 -> Winning ticket 12 (of 75) -> Run 0
59
+  Jobs:  (* job:0 timeleft:5 tix:75 ) (  job:1 timeleft:0 tix:--- )
60
+Random 281838 -> Winning ticket 63 (of 75) -> Run 0
61
+  Jobs:  (* job:0 timeleft:4 tix:75 ) (  job:1 timeleft:0 tix:--- )
62
+Random 755804 -> Winning ticket 29 (of 75) -> Run 0
63
+  Jobs:  (* job:0 timeleft:3 tix:75 ) (  job:1 timeleft:0 tix:--- )
64
+Random 618369 -> Winning ticket 69 (of 75) -> Run 0
65
+  Jobs:  (* job:0 timeleft:2 tix:75 ) (  job:1 timeleft:0 tix:--- )
66
+Random 250506 -> Winning ticket 6 (of 75) -> Run 0
67
+  Jobs:  (* job:0 timeleft:1 tix:75 ) (  job:1 timeleft:0 tix:--- )
68
+--> JOB 0 DONE at time 12
69
+```
70
+
71
+As you can see from this trace, what you are supposed to do is use the random
72
+number to figure out which ticket is the winner. Then, given the winning ticket,
73
+figure out which job should run. Repeat this until all of the jobs are finished
74
+running. It's as simple as that -- you are just emulating what the lottery
75
+scheduler does, but by hand!
76
+
77
+Just to make this absolutely clear, let's look at the first decision made in the
78
+example above. At this point, we have two jobs (job 0 which has a runtime of 8
79
+and 75 tickets, and job 1 which has a runtime of 4 and 25 tickets). The first
80
+random number we are given is 511275. As there are 100 tickets in the system,
81
+511275 \% 100 is 75, and thus 75 is our winning ticket.
82
+
83
+If ticket 75 is the winner, we simply search through the job list until we find
84
+it. The first entry, for job 0, has 75 tickets (0 through 74), and thus does not
85
+win; the next entry is for job 1, and thus we have found our winner, so we run
86
+job 1 for the quantum length (1 in this example). All of this is shown in the
87
+print out as follows:
88
+
89
+```text
90
+Random 511275 -> Winning ticket 75 (of 100) -> Run 1
91
+  Jobs:  (  job:0 timeleft:8 tix:75 ) (* job:1 timeleft:4 tix:25 )
92
+```
93
+
94
+As you can see, the first line summarizes what happens, and the second simply
95
+shows the entire job queue, with an * denoting which job was chosen.
96
+
97
+The simulator has a few other options, most of which should be self-explanatory.
98
+Most notably, the -l/--jlist flag can be used to specify an exact set of jobs
99
+and their ticket values, instead of always using randomly-generated job lists.
100
+
101
+```text
102
+prompt> ./lottery.py -h
103
+Usage: lottery.py [options]
104
+
105
+Options:
106
+  -h, --help            
107
+      show this help message and exit
108
+  -s SEED, --seed=SEED  
109
+      the random seed
110
+  -j JOBS, --jobs=JOBS  
111
+      number of jobs in the system
112
+  -l JLIST, --jlist=JLIST
113
+      instead of random jobs, provide a comma-separated list
114
+      of run times and ticket values (e.g., 10:100,20:100
115
+      would have two jobs with run-times of 10 and 20, each
116
+      with 100 tickets)
117
+  -m MAXLEN, --maxlen=MAXLEN
118
+      max length of job
119
+  -T MAXTICKET, --maxtick=MAXTICKET
120
+      maximum ticket value, if randomly assigned
121
+  -q QUANTUM, --quantum=QUANTUM
122
+      length of time slice
123
+  -c, --compute
124
+      compute answers for me
125
+
126
+```

+ 119
- 0
hw6/simu1/lottery.py 查看文件

@@ -0,0 +1,119 @@
1
+#! /usr/bin/env python
2
+
3
+import sys
4
+from optparse import OptionParser
5
+import random
6
+
7
+parser = OptionParser()
8
+parser.add_option('-s', '--seed', default=0, help='the random seed',              action='store', type='int', dest='seed')
9
+parser.add_option('-j', '--jobs', default=3, help='number of jobs in the system', action='store', type='int', dest='jobs')
10
+parser.add_option('-l', '--jlist', default='', help='instead of random jobs, provide a comma-separated list of run times and ticket values (e.g., 10:100,20:100 would have two jobs with run-times of 10 and 20, each with 100 tickets)',  action='store', type='string', dest='jlist')
11
+parser.add_option('-m', '--maxlen',  default=10,  help='max length of job',         action='store', type='int', dest='maxlen')
12
+parser.add_option('-T', '--maxticket', default=100, help='maximum ticket value, if randomly assigned',          action='store', type='int', dest='maxticket')
13
+parser.add_option('-q', '--quantum', default=1,   help='length of time slice', action='store', type='int', dest='quantum')
14
+parser.add_option('-c', '--compute', help='compute answers for me', action='store_true', default=False, dest='solve')
15
+
16
+(options, args) = parser.parse_args()
17
+
18
+random.seed(options.seed)
19
+
20
+print 'ARG jlist', options.jlist
21
+print 'ARG jobs', options.jobs
22
+print 'ARG maxlen', options.maxlen
23
+print 'ARG maxticket', options.maxticket
24
+print 'ARG quantum', options.quantum
25
+print 'ARG seed', options.seed
26
+print ''
27
+
28
+print 'Here is the job list, with the run time of each job: '
29
+
30
+import operator
31
+
32
+
33
+tickTotal = 0
34
+runTotal  = 0
35
+joblist = []
36
+if options.jlist == '':
37
+    for jobnum in range(0,options.jobs):
38
+        runtime = int(options.maxlen * random.random())
39
+        tickets = int(options.maxticket * random.random())
40
+        runTotal += runtime
41
+        tickTotal += tickets
42
+        joblist.append([jobnum, runtime, tickets])
43
+        print '  Job %d ( length = %d, tickets = %d )' % (jobnum, runtime, tickets)
44
+else:
45
+    jobnum = 0
46
+    for entry in options.jlist.split(','):
47
+        (runtime, tickets) = entry.split(':')
48
+        joblist.append([jobnum, int(runtime), int(tickets)])
49
+        runTotal += int(runtime)
50
+        tickTotal += int(tickets)
51
+        jobnum += 1
52
+    for job in joblist:
53
+        print '  Job %d ( length = %d, tickets = %d )' % (job[0], job[1], job[2])
54
+print '\n'
55
+
56
+if options.solve == False:
57
+    print 'Here is the set of random numbers you will need (at most):'
58
+    for i in range(runTotal):
59
+        r = int(random.random() * 1000001)
60
+        print 'Random', r
61
+
62
+if options.solve == True:
63
+    print '** Solutions **\n'
64
+
65
+    jobs  = len(joblist)
66
+    clock = 0
67
+    for i in range(runTotal):
68
+        r = int(random.random() * 1000001)
69
+        winner = int(r % tickTotal)
70
+
71
+        current = 0
72
+        for (job, runtime, tickets) in joblist:
73
+            current += tickets
74
+            if current > winner:
75
+                (wjob, wrun, wtix) = (job, runtime, tickets)
76
+                break
77
+
78
+        print 'Random', r, '-> Winning ticket %d (of %d) -> Run %d' % (winner, tickTotal, wjob)
79
+        # print 'Winning ticket %d (of %d) -> Run %d' % (winner, tickTotal, wjob)
80
+
81
+        print '  Jobs:',
82
+        for (job, runtime, tickets) in joblist:
83
+            if wjob == job:
84
+                wstr = '*'
85
+            else:
86
+                wstr = ' '
87
+
88
+            if runtime > 0:
89
+                tstr = tickets
90
+            else:
91
+                tstr = '---'
92
+            print ' (%s job:%d timeleft:%d tix:%s ) ' % (wstr, job, runtime, tstr), 
93
+        print ''
94
+
95
+        # now do the accounting
96
+        if wrun >= options.quantum:
97
+            wrun -= options.quantum
98
+        else:
99
+            wrun = 0
100
+
101
+        clock += options.quantum
102
+
103
+        # job completed!
104
+        if wrun == 0:
105
+            print '--> JOB %d DONE at time %d' % (wjob, clock)
106
+            tickTotal -= wtix
107
+            wtix = 0
108
+            jobs -= 1
109
+
110
+        # update job list
111
+        joblist[wjob] = (wjob, wrun, wtix)
112
+
113
+        if jobs == 0:
114
+            print ''
115
+            break
116
+
117
+
118
+
119
+

+ 72
- 0
hw6/task1/README.md 查看文件

@@ -0,0 +1,72 @@
1
+# README hw6-t1
2
+
3
+Schreiben Sie ein Programm, welches 2 Child's erzeugt. Der Parent übergibt an die beiden Childs über eine Pipe die Daten als Strings, die als Parameter beim Aufruf mit angegeben werden.
4
+
5
+Bei Aufruf des Programms geben Sie eine Zahlensequenz (`i32`) von mindestens 2 Zahlen an. Diese Zahlensequenz übergibt dann der Parent an die beiden Childs. Die Zahlen sind alle vom Typ `i32`.
6
+
7
+Werden zuwenig Parameter angegeben, so soll eine(!) Zeile 'Hilfe' ausgegeben werden, wie z.B.:
8
+
9
+```text
10
+Correct usage: number number <number> ...
11
+```
12
+
13
+> Wichtig: Nur eine Zeile 'Hilfe' ausgeben.
14
+
15
+Child1 berechnet aus der Zahlensequenz die Summe, Child2 das Produkt. Child1 und Child2 geben dann das Ergebnis auf der Konsole direkt aus.
16
+
17
+```text
18
+$ ./task1 -1 2 4 6 19 -100
19
+sending to childs: -1 2 4 6 19 -100
20
+Sum = -70
21
+Mul = 91200
22
+```
23
+
24
+> Format der Ausgabe genau einhalten. Das bedeutet, die Ausgabe des Childs mit der Summenberechnung muss immer VOR der Ausgabe des Childs mit der Multiplikation erfolgen. Welchen einfachen, allerdings Laufzeit 'schädlichen' Trick können Sie dafür benutzen, den Sie bereits kennen? Wie lässt sich der Parameter dafür tunen, um eine optimale Laufzeit (kurz!) des Programms zu erreichen. Kennen Sie eine Möglichkeit, ohne diesen Trick eine eindeutige Reihenfolge der Childs vorzugeben (muss nicht implementiert werden)?
25
+
26
+Werten Sie den Status der Childs im Elternprozess aus und beenden Sie das Programm nur im Erfolgsfall beider Childs mit dem Exit-Code 0. Erfolgsfall bedeutet dabei, dass der Child beendet wurde und den Exit Code 0 gesendet hat. Treten Fehler im Child auf, so sendet der Child z.B. den exit Code 1, so dass der Elternprozess dies auswerten kann und das Programm (Parent) ebenfalls mit exit Code 1 beendet.
27
+
28
+>Tip: waitpid() sollte dazu entsprechend ausgewertet werden.
29
+
30
+Beim Aufruf über cargo die '--' nicht vergessen, siehe dazu  **cargo help run**.
31
+
32
+Die Daten zwischen Eltern und Kindern werden als Byte-Stream gesendet. Achten Sie auf die Größe des Puffers, den die Childs zum Empfangen anlegen. Definieren Sie für diese Größe eine `const` und setzen Sie diese auf 256.
33
+
34
+Da die eingelesenen Argumente bereits als Strings vorliegen, bietet es sich an im Programm intern mit Strings zu arbeiten. Damit ergeben sich folgende Hilfsfunktionen:
35
+
36
+- `concatenate_strings()` :
37
+- `split_into_strings()`:
38
+- `sum_strings()`:
39
+- `mul_strings()`:
40
+
41
+> Achten Sie auf einen geeignet grossen Rückgabewert im Erfolgsfall, insbesondere in der Funktion, die die Multiplikation ausführt (`mul_strings()`).
42
+
43
+> Achten Sie aber bei Ihren Berechnungen auch darauf, das kein Overflow auftreten kann, der das Programm abbricht (siehe Rust Standarddoku: `overflowing`). Wenn einen Berechnung aufgrund eines auftretenden Overflows nicht durchgeführt werden kann, so gibt der Child einen Fehler aus und terminiert - wie immer im Fehlerfall -  mit dem Exitcode '1'. Die Ausgabe des Programms könnte dann z.B. folgendermassen aussehen:
44
+
45
+```text
46
+> cargo run -- 1000 1000 1000 1000 1000 1000 1000
47
+...
48
+Sum = 7000
49
+... Overflow would happen in mul_strings()
50
+...
51
+```
52
+
53
+> ... steht für mögliche andere Ausgaben
54
+
55
+Darüber hinaus sollten Sie bei Code-Wiederholungen prinzipiell immer überlegen, welche weiteren Hilfsfunktionen sich dadurch anbieten.
56
+
57
+## Externe Crates
58
+
59
+Benutzen Sie für Ihre Implementierung nur die externe Crate `nix`.
60
+
61
+## Module und Tests
62
+
63
+Ob und wie Sie den Code in Module aufteilen wollen ist Ihnen überlassen. Schreiben Sie jedoch Ihre Unit-Tests in der Datei `unit_test_pipe.rs` oder als eigenständigen Test, der von 'cargo test' aufgerufen wird, siehe auch [Testing][]. Einfache Tests können auch direkt in die Dokumentation 'codiert' werden, siehe [Documentation Tests][]
64
+
65
+Achten Sie beim Exit Code des Elternprozesses darauf, dass dieser nur 0 ist, wenn BEIDE Childs jeweils einen exit Code 0 zurücksenden.
66
+
67
+## Dokumentation
68
+
69
+Es ist ausreichend, wenn Sie Ihre Dokumentation im Code soweit ergänzen, dass dieser nachvollziehbar ist. Eine Dokumentation über `cargo doc` muss nicht erstellt werden.
70
+
71
+[Testing]: https://doc.rust-lang.org/book/testing.html
72
+[Documentation Tests]: https://doc.rust-lang.org/book/testing.html#documentation-tests

+ 72
- 0
hw6/task1/tests/output.bats 查看文件

@@ -0,0 +1,72 @@
1
+#!/usr/bin/env bats
2
+
3
+
4
+@test "task1: Check that we have a debug output" {
5
+    run stat "$BATS_TEST_DIRNAME/../target/debug/task1"
6
+    [ "$status" -eq 0 ]
7
+}
8
+
9
+# Check lines of output
10
+
11
+
12
+# wc output with white spaces is trimmed by xargs
13
+@test "task1: Output with to no paras must be exact 1 line long" {
14
+    run bash -c "'$BATS_TEST_DIRNAME/../target/debug/task1' 1 | wc -l | xargs"
15
+    [ "$output" = "1" ]
16
+
17
+}
18
+
19
+# wc output with white spaces is trimmed by xargs
20
+@test "task1: Output with one para must be exact 1 line long" {
21
+    run bash -c "'$BATS_TEST_DIRNAME/../target/debug/task1' -1 | wc -l | xargs"
22
+    [ "$output" = "1" ]
23
+
24
+}
25
+
26
+
27
+# wc output with white spaces is trimmed by xargs
28
+@test "task1: Output with correct para must be exact 3 line long" {
29
+    run bash -c "'$BATS_TEST_DIRNAME/../target/debug/task1' 2 3 4 5 6 7 8 9 | wc -l | xargs"
30
+    [ "$output" = "3" ]
31
+}
32
+
33
+
34
+# Check results
35
+
36
+@test "task1: 1 -2 3 -4 5" {
37
+    run "$BATS_TEST_DIRNAME/../target/debug/task1" 1 -2 3 -4 5
38
+    [[ "${lines[0]}" =~ "sending to childs: 1 -2 3 -4 5" ]]
39
+    [[ "${lines[1]}" =~ "Sum = 3" ]]
40
+    [[ "${lines[2]}" =~ "Mul = 120" ]]
41
+}
42
+
43
+@test "task1: 1 -2 3 -4 5 100 -400 5332 3290 -22 -4646 -1 -1" {
44
+    run "$BATS_TEST_DIRNAME/../target/debug/task1" 1 -2 3 -4 5 100 -400 5332 3290 -22 -4646 -1 -1
45
+    [[ "${lines[0]}" =~ "sending to childs: 1 -2 3 -4 5 100 -400 5332 3290 -22 -4646 -1 -1" ]]
46
+    [[ "${lines[1]}" =~ "Sum = 3655" ]]
47
+    [[ "${lines[2]}" =~ "Mul = -8606551312128000000" ]]
48
+}
49
+
50
+@test "task1: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" {
51
+    run "$BATS_TEST_DIRNAME/../target/debug/task1" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
52
+    [[ "${lines[0]}" =~ "sending to childs: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" ]]
53
+    [[ "${lines[1]}" =~ "Sum = 210" ]]
54
+    [[ "${lines[2]}" =~ "Mul = 2432902008176640000" ]]
55
+}
56
+
57
+# Status checks
58
+@test "task1: Output with wrong args (1) does not crash" {
59
+    run bash -c "'$BATS_TEST_DIRNAME/../target/debug/task1' a b c "
60
+    [ "$status" = 1 ]
61
+}
62
+
63
+# Status checks
64
+@test "task1: Output with wrong args (2) does not crash" {
65
+    run bash -c "'$BATS_TEST_DIRNAME/../target/debug/task1' 1 - 2 "
66
+    [ "$status" = 1 ]
67
+}
68
+
69
+@test "task1: Overflow Output does not crash" {
70
+    run bash -c "'$BATS_TEST_DIRNAME/../target/debug/task1' 10000 10000 10000 10000 10000"
71
+    [ "$status" = 1 ]
72
+}

+ 177
- 0
hw6/task2/README.md 查看文件

@@ -0,0 +1,177 @@
1
+# README hw6-t2
2
+- [Einleitung](#einleitung)
3
+- [Implementierung](#implementierung)
4
+    - [*Shell* Datenstruktur](#shell-datenstruktur)
5
+        - [Public Methode *new()*](#public-methode-new)
6
+        - [Public Methode *start() -> Result*](#public-methode-start---result)
7
+        - [Private Methode *shell_loop() -> Result*](#private-methode-shellloop---result)
8
+        - [Private Methode *prompt() -> Result<Option<String>, ...>*](#private-methode-prompt---resultoptionstring)
9
+        - [Private Methode *run()*](#private-methode-run)
10
+    - ['Command Datenstruktur'](#command-datenstruktur)
11
+- [Externe Crates](#externe-crates)
12
+- [Module und Tests](#module-und-tests)
13
+- [Dokumentation](#dokumentation)
14
+
15
+## Einleitung
16
+
17
+In dieser Aufgabe werden Sie beginnen, eine einfache, aber robuste Shell zu erstellen. In den nächsten Aufgaben werden Sie diese Shell dann weiter ausbauen.
18
+
19
+Dafür werden zu Beginn 2 Objekte (Structs) benötigt:
20
+
21
+- Shell
22
+- Command
23
+
24
+Die Unit Tests dieser Objekte schreiben Sie bitte wieder in eigene Dateien, siehe folgendes Diagramm:
25
+
26
+![Module](png/shell_module.png)
27
+
28
+Die Methoden der Datenstruktur Shell beinhalten das Setup und die Loop einer Shell. In der Loop wird der Prompt ausgegeben und auf die Eingaben des Benutzers gewartet. Schließt der Benutzer seine Eingabe mit RETURN ab, so wertet eine Methode die Eingabezeile aus und erstellt daraus die vom Benutzer eingegeben Kommandos. Diese Loop kann der Benutzer mit dem Kommando `exit` beenden.
29
+
30
+Das Handling der Kommandos übernehmen die Methoden der Datenstruktur Command. Beachten Sie, Ihre derzeitige Shell, mit der Sie selbst im Labor arbeiten (labshell), wird nicht einfach beendet wenn ein Kommando fehlerhaft ist oder nicht ausgeführt werden kann. Ebenso soll Ihre Shell nur bei dem Kommando `exit` beendet werden.
31
+
32
+Die main() Funktion selbst hat somit wenig zu tun, sie muss lediglich eine Instanz des Datentyps Shell starten und das Ergebnis auswerten. Ist das Ergebnis 'Ok' so wird das Programm mit dem Exitcode 0 beendet. Tritt intern bei der Benutzung einer Funktionalität Ihrer Shell ein Fehler auf, welcher nicht von Ihnen behandelt werden kann, so wird die Shell mit dem Exitcode 1 beendet.
33
+
34
+```Rust
35
+...
36
+let mut s = Shell::new(..);
37
+    match s.start() {
38
+        Ok(_) => process::exit(0),
39
+        Err(_) => process::exit(1),
40
+    }
41
+...
42
+```
43
+
44
+## Implementierung
45
+
46
+### *Shell* Datenstruktur
47
+
48
+Die Datenstruktur 'Shell' hat folgende Felder:
49
+
50
+```Rust
51
+struct Shell<R, W> {
52
+    pub reader: R,
53
+    pub writer: W,
54
+    pub should_exit: bool,
55
+    pub name: String,
56
+}
57
+```
58
+
59
+'reader' ist der Input Kanal Ihrer Shell und 'writer' der Output Kanal. Das Flag 'should_exit' signalisiert Ihrer Loop (siehe unten), dass die Loop beendet werden soll. 'name' ist der Name der Shell, den diese immer am Anfang des Prompts ausgibt, damit diese sich von Ihrer normalen Shell unterscheidet.
60
+
61
+Für Ihre Implementierung verwenden Sie folgende Traitbounds:
62
+
63
+- R: BufRead
64
+- W: Write
65
+
66
+Um das Verhalten einer echten Shell zu erhalten benötigen Sie für den Reader die Standard-Eingabe und für den Writer die Standard-Ausgabe.
67
+
68
+In den Unit Tests werden andere Typen für R und W benutzt. Schauen Sie sich dazu die Tests in `unit_tests_shell.rs` an. Diese sollen Ihnen als Beispiel für eigene Tests dienen.
69
+
70
+#### Public Methode *new()*
71
+
72
+```Rust
73
+pub fn new(input: R, output: W, name: String) -> Self
74
+```
75
+
76
+Eine Instanz der Shell wird erstellt.
77
+
78
+#### Public Methode *start() -> Result*
79
+
80
+Die public Methode *start()* ruft die private Methode *shell_loop()* auf. Die Funktion *start()* ist für evtl. spätere Erweiterungen gedacht, wenn in der Shell vor dem Starten der Loop noch weitere Initialisierungsarbeiten durchzuführen sind.
81
+
82
+#### Private Methode *shell_loop() -> Result*
83
+
84
+Die Basis Loop einer Shell haben wir bereits in der Vorlesung besprochen. Die Loop wartet auf Eingaben des Benutzers, die dieser mit RETURN abschließt. Die Eingaben werden dann ausgewertet und das Kommando, das sich u.U. daraus ergibt ausgeführt. Danach steht die Loop wieder für Eingaben bereit.
85
+
86
+![Loop](png/shell_loop.png)
87
+
88
+#### Private Methode *prompt() -> Result<Option<String>, ...>*
89
+
90
+Die Funktion gibt den Namen der Shell, den aktuellen Pfad (siehe `std::env`) sowie das ' >' Zeichen, gefolgt von einem Leerzeichen aus:
91
+
92
+```text
93
+bsys-shell /Users/username >
94
+```
95
+
96
+Nach der Ausgabe wartet die Funktion auf Eingaben des Benutzers.
97
+
98
+Benutzen Sie zum Einlesen die *read_line()* Funktion. Liefert *read_line()* kein Zeichen, so gibt die Funktion None zurück. Werden Zeichen von *read_line()* eingelesen, so liefert die Funktion die Eingabe des Benutzers zurück.
99
+
100
+> Damit bei einem *write!()* Aufruf auf der Konsole auch der String erscheint, muss nach dem *write!()* Aufruf ein *flush()* Aufruf folgen
101
+
102
+Wie am Funktionskopf zu erkennen ist, liefert diese Funktion im Erfolgsfall den eingelesenen String zurück.
103
+
104
+#### Private Methode *run()*
105
+
106
+Diese Methode wird von *shell_loop()* aufgerufen, wenn der Benutzer Eingaben gemacht hat, und daraus ein Kommando geparst werden konnte. Das eigentliche Parsen des Strings geschieht im 'Command' Modul. Das Modul liefert nach einem erfolgreichen Parse-Vorgang ein Kommando zurück, welches dann in run() aufgerufen wird.
107
+
108
+Sowohl die Funktion zum Parsen des Strings, als auch die Methoden um die eigentlichen Kommandos auszuführen, platzieren Sie bitte in die Datei `command.rs`.
109
+
110
+### 'Command Datenstruktur'
111
+
112
+Die Datenstruktur 'Command' in `command.rs` hat zunächst folgende Felder:
113
+
114
+```Rust
115
+enum Command {
116
+    Empty,
117
+    Exit,
118
+    Cd(Option<OsString>),
119
+}
120
+```
121
+
122
+Die Kommandos bedeuten:
123
+
124
+- 'Empty': Keine Eingabe, z.B. hat der Benutzer nur Return, Leerzeichen oder andere Whitespaces getippt.
125
+- 'Exit': Dieses Kommando bedeutet, dass sich die Shell vor dem Start der nächsten Loop beendet.
126
+- 'Cd': ein Change Directory Befehl soll ausgeführt werden. *Option* enthält den Pfad zu welchem gewechselt werden soll.
127
+
128
+
129
+Im Modul `command.rs` wird die Trait Methode `FromStr` bereit gestellt, so dass das Objekt Command dieses Trait unterstützt.
130
+
131
+```Rust
132
+...
133
+fn from_str(s: &str) -> Result<Command, .....> {
134
+```
135
+
136
+Diese Funktion können Sie benutzen, um sich den Input des Users in der Shell analysieren zu lassen. Als Rückgabewert erhalten Sie den entsprechenden `Command`. Somit können Sie im Modul `shell.rs` komfortabel über folgenden Aufruf den Input String in ein Kommando wandeln lassen:
137
+
138
+```Rust
139
+Command::from_str(&line).and_then(|cmd| self.run(cmd))
140
+```
141
+
142
+In der obigen Trait Methode können die einfachen Kommandos wie:
143
+
144
+- Empty und
145
+- Exit
146
+
147
+direkt - je nach Input des User - zurück gegeben werden. Für komplexere Funktionen wie **cd** bietet es sich an, in der Trait Methode spezifische Methoden des Datentyps Command aufzurufen. Für jedes Kommando sind in der command.rs zwei Methoden zu implementieren:
148
+
149
+- *parse_<command>()*
150
+- *exec_<command>()*
151
+
152
+Für das **cd** Kommando somit:
153
+
154
+- *parse_cd()*
155
+- *exec_cd()*
156
+
157
+Die *parse_command()* Methode wird im *from_str()* Trait aufgerufen. Die *exec_<commnad>()>* Methode wird aus dem shell Modul an geeigneter Stelle aufgerufen.
158
+
159
+> Ihr **cd** Kommando sollte sich so verhalten wie das **cd** Kommando der labshell. Somit kommen Sie beim Aufruf von **cd** ohne Parameter in Ihr Home Verzeichnis (siehe env::var_os("HOME")). Mit **cd ..** in das Verzeichnis 'darüber' usw.
160
+
161
+
162
+## Externe Crates
163
+
164
+Benutzen Sie für Ihre Implementierung nur die externe Crate `nix`.
165
+
166
+## Module und Tests
167
+
168
+Ob und wie Sie den Code in weitere Module aufteilen wollen ist Ihnen überlassen. Schreiben Sie jedoch Ihre Unit-Tests in der Datei `unit_test_shell.rs` oder als eigenständigen Test, der von 'cargo test' aufgerufen wird, siehe auch [Testing][]. Einfache Tests können auch direkt in die Dokumentation 'codiert' werden, siehe [Documentation Tests][].
169
+
170
+Wichtig: Erstellen Sie ausreichend Unit Tests, um möglichst alle Methoden aus `shell.rs` und `command.rs` ausreichend testen zu können.
171
+
172
+## Dokumentation
173
+
174
+Bei dieser Aufgabe ist Ihre Dokumentation wichtig, um Ihren Programmablauf nachvollziehen zu können. Bitte dokumentieren Sie Ihre Funktionen entsprechend umfangreicher und kommentieren Sie spezielle Kniffe im Code, die Sie verwendet haben.
175
+
176
+[Testing]: https://doc.rust-lang.org/book/first-edition/testing.html
177
+[Documentation Tests]: https://doc.rust-lang.org/book/first-edition/testing.html#documentation-tests

二进制
hw6/task2/png/shell_loop.png 查看文件


二进制
hw6/task2/png/shell_module.png 查看文件


+ 55
- 0
hw6/task2/src/unit_tests_shell.rs 查看文件

@@ -0,0 +1,55 @@
1
+#[cfg(test)]
2
+mod test {
3
+    use shell::Shell;
4
+
5
+    #[test]
6
+    fn test_prompt_in_memory_with_string() {
7
+        let input = b"echo";
8
+        let mut output = Vec::new();
9
+
10
+        let mut shell = Shell {
11
+            reader: &input[..],
12
+            writer: &mut output,
13
+            should_exit: false,
14
+            name: "bsys-shell".to_string(),
15
+        };
16
+
17
+        let output = shell.prompt().unwrap().unwrap();
18
+
19
+        assert_eq!("echo", output);
20
+    }
21
+
22
+    #[test]
23
+    fn test_loop_of_shell_which_wants_to_exit() {
24
+        let input = b"BlaBla";
25
+        let mut output = Vec::new();
26
+
27
+        let mut shell = Shell {
28
+            reader: &input[..],
29
+            writer: &mut output,
30
+            should_exit: true,
31
+            name: "bsys-shell".to_string(),
32
+        };
33
+
34
+        let output = shell.start().unwrap();
35
+
36
+        assert_eq!((), output);
37
+    }
38
+
39
+    #[test]
40
+    fn test_loop_with_exit_command() {
41
+        let input = b"exit";
42
+        let mut output = Vec::new();
43
+
44
+        let mut shell = Shell {
45
+            reader: &input[..],
46
+            writer: &mut output,
47
+            should_exit: false,
48
+            name: "bsys-shell".to_string(),
49
+        };
50
+
51
+        let output = shell.start().unwrap();
52
+
53
+        assert_eq!((), output);
54
+    }
55
+}

正在加载...
取消
保存