# 2018q3 Homework2 (lab0)
contributed by < [`letticee`](https://github.com/letticee) >
>* 請持續更新進度!
>* [Fix code command](v) 應修正為 "Modify comment"
>
>[name=課程助教][color=red]
>>已改
作業說明:[E01: lab0](https://hackmd.io/s/BJp_jq-tm)
* 實驗環境
* 學習 git 與 開發工具
* 實作內容
* 研究自動測試機制
## 實驗環境
```shell
$ uname -a
Darwin Cynthia-8.local 17.7.0 Darwin Kernel Version 17.7.0: Thu Jun 21 22:53:14 PDT 2018; root:xnu-4570.71.2~1/RELEASE_X86_64 x86_64
```
## 學習 git 與 開發工具
###
### 遇到的問題
* 在 macOS 上跑 cppcheck 時,會出現一個錯誤,導致無法 commit
:::danger
[harness.c:147]: (error) Address of auto-variable 'new_block->payload' returned
Fail to pass static analysis.
:::
猜測是因為他 return 了一個 local variable 的reference,出function就會消失了 (?
* 目前處理方法:到 `./lab0-c/scripts/pre-commit.hook` 加入一個 cppcheck 的 option --suppress 來擋住那個 error
:::success
```shell
CPPCHECK_OPTS="-I. -I./include --error-exitcode=1 . --suppress=*:./harness.c:147 ./"
```
:::
## 實作內容
### [queue.h](https://github.com/letticee/lab0-c/blob/7c1d71c20769347cd165529dfc030b316ae3b013/queue.h#L19)
```c=
/* Queue structure */
typedef struct {
list_ele_t *head; /* Linked list of elements */
list_ele_t *tail;
int size;
} queue_t;
```
* 在 `queue_t` 中加入
* `list_ele_t *tail`
* 指向 queue 的尾端,使 `q_insert_tail()` 可以在常數時間內完成
* `int size`
* 紀錄 queue 的大小,使 `q_size()` 可以在常數時間內回傳 queue 的大小
### [q_new()](https://github.com/letticee/lab0-c/blob/7c1d71c20769347cd165529dfc030b316ae3b013/queue.c#L25)
1. 當 malloc 失敗時回傳 `NULL`
2. 初始化 `head = NULL`, `tail = NULL`, `size = 0`
### [q_free()](https://github.com/letticee/lab0-c/blob/7c1d71c20769347cd165529dfc030b316ae3b013/queue.c#L40)
1. q 是 NULL 的話直接 return
2. q 不是 empty 的話,先將 head 的指標複製一份到 `ele`,將 `head` 指標指到下一個元素。再 free 掉 `ele` 所指到的 value 跟 `ele` 指標
3. free 掉 q
* $T(n) = O(n)$
### [q_insert_head()](https://github.com/letticee/lab0-c/blob/7c1d71c20769347cd165529dfc030b316ae3b013/queue.c#L65)
1. q 是 `NULL` 的話回傳 `false`
2. malloc 新的 head 的空間,如果失敗就回傳 `false`
3. malloc 新的 head 的 value 空間,如果失敗 free 掉新的 head 、回傳 `false`
4. 將傳入的字串複製到新的 head 的 value
5. 將新的 head 的 `next` 指到原本 q 的 `head`,然後將 q 的 `head` 指到新的 head
6. 如果 q 的 `head` 是 `NULL` (原本是 empty queue),將 `tail` 也指到新的 head
7. 將 q 的 `size` 加 1
* $T(n) = O(1)$
* 在配置空間和複製字串的部分,原本想要使用 `strdup()` 做到配置空間和複製字串的功能,但因為在 `harness.h` 裡有 define 使用自定義的 `test_malloc()` 取代 `malloc()`。實驗過後,發現 `strdup()` 在配置空間的時候不會呼叫到自定義的 `test_malloc()`,所以會造成 test 不過。
* `strdup()` 版本
```c=
(newh->value) = strdup(s);
if ((newh->value) == NULL) {
free(newh);
return false;
}
```
* `malloc()` + `strcpy()` 版本
```c=
newh->value = malloc(sizeof(char) * (strlen(s) + 1));
if (newh->value == NULL) {
free(newh);
return false;
}
strcpy(newh->value, s);
```
### [q_insert_tail()](https://github.com/letticee/lab0-c/blob/406eaf85a8e511dc231f0dfe0b542a9db3d78639/queue.c#L109)
1. q 是 `NULL` 的話回傳 `false`
1. malloc 新的 tail 的空間,如果失敗就回傳 `false`
1. malloc 新的 tail 的 value 空間,如果失敗 free 掉新的 tail 、回傳 `false`
1. 將傳入的字串複製到新的 tail 的 value
1. 將原本 q 的 `head` 的 `next` 指到新的 tail,新的 tail 的 `next` 指到 `NULL`
1. 將 q 的 `tail` 指到新的 tail
1. 將 q 的 `size` 加 1
* $T(n) = O(1)$
### [q_remove_head()](https://github.com/letticee/lab0-c/blob/406eaf85a8e511dc231f0dfe0b542a9db3d78639/queue.c#L149)
1. q 是 `NULL` 或 empty ( `head` 是 `NULL` )的話回傳 `false`
2. 如果 sp 不是 `NULL`,將 `sp` 空間內的 `bufsize` 大小範圍都設為 `'\0'`,然後將 `head` 的 `value` 中 `bufsize - 1` 大小的字串複製到 `sp`
3. 先將原本的 `head` 的指標複製一份到 `oldh`,將 `head` 指到原本的 head 的下一個元素。再 free 掉 `oldh` 所指到的 `value` 跟 `oldh` 指標
4. 將 q 的 size 減 1
* $T(n) = O(1)$
### [q_size()](https://github.com/letticee/lab0-c/blob/406eaf85a8e511dc231f0dfe0b542a9db3d78639/queue.c#L174)
1. q 是 `NULL` 或 empty ,回傳 0
2. 回傳 q 的 `size`
* $T(n) = O(1)$
### [q_reverse()](https://github.com/letticee/lab0-c/blob/406eaf85a8e511dc231f0dfe0b542a9db3d78639/queue.c#L190)
1. q 是 `NULL` 或 empty 的話,直接 return
2. 初始化 `pre`, `itr`, `post` 三個指標,分別指到 `NULL`, `head`, `itr->next`,然後將 `tail` 指到 `head`
3. 迴圈內將 `itr` 的 `next` 指到 `pre`,`pre` 指到 `itr` ,`itr` 指到 `post`,`post` 指到 `post` 的 `next` ( 將所有元素的 `next` 指到前一個元素 ),直到 `post` 是 `NULL`,也就是 `itr` 指到的是 q 的最後一個元素
4. 將 `itr` 的 `next` 指到 `pre`,將 `head` 指到 `itr`
* $T(n) = O(n)$
## 研究自動測試機制
### Makefile
* 從 `Makefile` 可以得知 `make` 完後會產生一個 `qtest` 的可執行檔
* `make test` 指令會去執行 `driver.py`
* `make clean` 指令會清除 `make` 產生的檔案
### 自動評分系統運作的原理
* 自動評分的主要程式是 `driver.py`
* `driver.py`
* 首先會呼叫 `run()`,`run()` 主要是處理參數(`-h` `-p` `-t` `-v` `-a`),接著呼叫`Tracer.run()`
* `-h`:印出參數說明
* `-p`:要測試的程式,預設是 `./qtest`
* `-t`:要測試的命令在 traceDict 裡的編號
* `-v`:測試訊息的詳細程度(0 - 3),預設是 1
* `-v 0` 參數:僅列出每個測驗的分數
```
--- Trace Points
--- trace-01-ops 6/6
--- trace-02-ops 6/6
--- trace-03-ops 6/6
--- trace-04-ops 6/6
--- trace-05-ops 6/6
--- trace-06-string 7/7
--- trace-07-robust 7/7
--- trace-08-robust 7/7
--- trace-09-robust 7/7
--- trace-10-malloc 7/7
--- trace-11-malloc 7/7
--- trace-12-malloc 7/7
--- trace-13-perf 7/7
--- trace-14-perf 7/7
--- trace-15-perf 7/7
--- TOTAL 100/100
```
`-v 1`:多印出測驗內容
```
--- Trace Points
+++ TESTING trace trace-01-ops:
# Test of insert_head and remove_head
--- trace-01-ops 6/6
+++ TESTING trace trace-02-ops:
# Test of insert_head, insert_tail, and remove_head
--- trace-02-ops 6/6
+++ TESTING trace trace-03-ops:
# Test of insert_head, insert_tail, reverse, and remove_head
--- trace-03-ops 6/6
```
`-v 2`:多印出測驗的指令內容
```
--- Trace Points
+++ TESTING trace trace-01-ops:
cmd># Test of insert_head and remove_head
cmd>option fail 0
cmd>option malloc 0
cmd>new
cmd>ih gerbil
cmd>ih bear
cmd>ih dolphin
cmd>rh dolphin
Removed dolphin from queue
cmd>rh bear
Removed bear from queue
cmd>rh gerbil
Removed gerbil from queue
cmd>
--- trace-01-ops 6/6
```
`-v 3`:多印出 queue 裡面儲存的內容
```
--- Trace Points
+++ TESTING trace trace-01-ops:
cmd># Test of insert_head and remove_head
cmd>option fail 0
cmd>option malloc 0
cmd>new
q = []
cmd>ih gerbil
q = [gerbil]
cmd>ih bear
q = [bear gerbil]
cmd>ih dolphin
q = [dolphin bear gerbil]
cmd>rh dolphin
Removed dolphin from queue
q = [bear gerbil]
cmd>rh bear
Removed bear from queue
q = [gerbil]
cmd>rh gerbil
Removed gerbil from queue
q = []
cmd>
Freeing queue
--- trace-01-ops 6/6
```
* `-A`:在最後產生 JSON 格式的成績單
* `Tracer.run()` 中會將 trace-01 -- trace-15 依序執行一次 `Tracer.runTrace()`,根據回傳值判斷該測驗的分數、計算總分並印出成績
* `Tracer.runTrace()` 呼叫 `subprocess.call()` 並傳入參數來執行 `qtest`
### qtest 的行為和裡頭的技巧
* `qtest.c`
* 處理參數(`-h` `-f` `-v` `-l`)
* `-h`:印出參數說明
* `-f`:要讀入的 command 檔
* `-v`:測試訊息的詳細程度
* `-l`:將測試訊息輸出成檔
* 在 `console_init()` 內呼叫 `add_cmd()` 來加入 cmd 指令
* 處理每個指令的實作內容,包括檢查指令的參數、呼叫 `queue` 的 function 去操作 q、輸出警告和錯誤訊息(包括檢查 `queue.c` 的實作是否會發生 segmentation fault 和 timeout)
* `test_malloc()` 和 `test_free()`
* 在 `harness.h` 中可以看到有 define 使用自定義的 `test_malloc()` 取代 `malloc()`、 `test_free()` 取代 `free()`
* 在 `harness.c` 的 `test_malloc()` 中會呼叫 `fail_allocation()` 根據現在的 `fail_probability` 機率回傳 `NULL` 。並將 malloc 的空間以 double linked list 儲存,以知道 malloc 了幾個 block (?,`test_free()` 也是從 linked list 去釋放空間