2020年1月18日 星期六

[Linux 文章收集] How to use the history command in Linux

Source From Here
Preface
As I spend more and more time in terminal sessions, it feels like I'm continually finding new commands that make my daily tasks more efficient. The GNU history command is one that really changed my work day. The GNU history command keeps a list of all the other commands that have been run from that terminal session, then allows you to replay or reuse those commands instead of retyping them. If you are an old greybeard, you know about the power of history, but for us dabblers or new sysadmin folks, history is an immediate productivity gain.

History 101
To see history in action, open a terminal program on your Linux installation and type:
$ history

Here's the response I got:
1 clear
2 ls -al
3 sudo dnf update -y
4 history

The history command shows a list of the commands entered since you started the session. The joy of history is that now you can replay any of them by using a command such as:
$ !3

The !3 command at the prompt tells the shell to rerun the command on line 3 of the history list. I could also access that command by entering:
$ history | grep "command pattern"


Searching history
You can also use history to rerun the last command you entered by typing !!. And, by pairing it with grep, you can search for commands that match a text pattern or, by using it with tail, you can find the last few commands you executed. For example:
$ history | grep dnf
3 sudo dnf update -y
5 history | grep dnf


$ history | tail -n 3
4 history
5 history | grep dnf
6 history | tail -n 3

Another way to get to this search functionality is by typing Ctrl-R to invoke a recursive search of your command history. After typing this, the prompt changes to:
(reverse-i-search)`':

Now you can start typing a command, and matching commands will be displayed for you to execute by pressing Return or Enter.

Changing an executed command
history also allows you to rerun a command with different syntax. For example, if I wanted to change my previous command history | grep dnf to history | grep ssh, I can execute the following at the prompt:
$ ^dnf^ssh^

history will rerun the command, but replace dnf with ssh, and execute it.

Removing history
There may come a time that you want to remove some or all the commands in your history file. If you want to delete a particular command, enter history -d . To clear the entire contents of the history file, execute history -c. The history file is stored in a file that you can modify, as well. Bash shell users will find it in their Home directory as .bash_history.

Next steps
There are a number of other things that you can do with history:
* Set the size of your history buffer to a certain number of commands
* Record the date and time for each line in history
* Prevent certain commands from being recorded in history

For more information about the history command and other interesting things you can do with it, take a look at the GNU Bash Manual.

[Git 文章收集] Git Submodule 介紹與使用

Source From Here
git Submodule 使用時機
大家在開發新專案的時候,不知道有沒有用到其他專案的程式碼,像是 Web 專案,也許會用到 Blueprintcss CSS Framwork 或者是 Sass,這些專案目前都放在 Github 上面進行維護,以前的作法就是先 git clone 下來,把要的檔案分別複製到自己專案,可是問題來了,如果官方更新了程式碼,那自己的專案如何更新呢?難道是重複步驟把檔案複製到原來地方嗎?這樣會不會太麻煩,這時候就是需要 git submodule 來幫助大家進行程式碼的更新,這樣隨時隨地都可以取得最新的程式碼。補充說明一點,git 目前無法針對單一專案底下的單一檔案或目錄進行 clone,而必須 clone 整個目錄,這點跟 svn 有很大的不同,所以 git 可以建立各個不同的 submodule 來整合成一個大型 Project。換句話說就是: 在您的專案底下,你可以任意將其他人的專案掛載在自己任何目錄底下。

建立 Git Submodule
在練習 git 指令之前請先註冊好 github 帳號,並且開一個測試 repository,建立 Submodule 非常容易,語法如下 (more):


實際指令範例:
$ git submodule add https://github.com/appleboy/CodeIgniter-TW-Language user_guide

下這指令之前請注意最後面的 path 部份,請勿先建立空的目錄,也就是如果該目錄存在,就會衝突,所以並不需要額外幫 module 建立目錄,指令完成結果如下:
Cloning into user_guide...
remote: Counting objects: 32, done.
remote: Compressing objects: 100% (19/19), done.
remote: Total 32 (delta 12), reused 32 (delta 12)
Unpacking objects: 100% (32/32), done.

這時候在目錄底下打入指令 git status,你會發現多了兩個檔案需要 commit:
  1. # On branch master  
  2. # Changes to be committed:  
  3. #   (use "git reset HEAD ..." to unstage)  
  4. #  
  5. #       new file:   .gitmodules  
  6. #       new file:   user_guide  
  7. #
  注意第一個檔案 .gitmodules,裡面紀錄 submodule 的對應關係,我們實際打開看內容:
  1. [submodule "user_guide"]  
  2.     path = user_guide  
  3.     url = https://github.com/appleboy/CodeIgniter-TW-Language  
裡面寫的很清楚,之後如果要清除 sub module 也是要從這檔案移除相關設定,接著就是直接 commit 到 project 底下吧:
$ git commit -a -m "first commit with submodule codeigniter user guide" && git push

接著回去看 github 網站就會多出一個小圖示了:


最後還是需要初始化 init submodule,透過底下指令來達成,否則 git 不知道你有新增 module
$ git submodule init

clone project with Git Submodule
我們還是拿上面的例子來測試,首先還是一樣用 git clone 來下載程式碼:
$ git clone git@github.com:appleboy/test.git test2

可是你有沒有發現 user_guide 這 sub module 是空目錄,這時候就是要透過 git submodule 來下載程式碼:
# git submodule update
Cloning into user_guide...
remote: Counting objects: 32, done.
remote: Compressing objects: 100% (19/19), done.
remote: Total 32 (delta 12), reused 32 (delta 12)
Unpacking objects: 100% (32/32), done.
Submodule path 'user_guide': checked out '7efead6378993edfaa0c55927d4a4fdf629c4726'

更新已安裝 module
一樣切換到 sub module 目錄,接著做 git pull:
# cd user_guide/
# git pull origin master

這時候我們切回去上層目錄,執行 git status:
  1. # On branch master  
  2. # Changed but not updated:  
  3. #   (use "git add ..." to update what will be committed)  
  4. #   (use "git checkout -- ..." to discard changes in working directory)  
  5. #  
  6. #       modified:   user_guide (new commits)
  我們有了 new commit,有沒有發現與 git submodule add 的時候一樣,這時候我們需要同步 sub module commit ID 到 parent,所以一樣執行 git commit && git push 即可。
# git commit -a -m "first commit with submodule codeigniter user guide" && git push

最後可以透過 statu 來看看是否有相同的 commit ID:
# git submodule status

移除 Sub module
移除方式非常容易,上面有提到的檔案都必需要經過修改

1. 移除目錄
# git rm --cached user_guide
# git rm user_guide

2. 修改 .gitmodules,移除不需要的 module
# vi .gitmodules

3. 修改 .git/config,移除 submodule URL
# vi .git/config

4. 執行 commit
# git add . && git commit -m "Remove sub module"

5. 最後 syn module 資料
# git submodule sync


總結歸納
git submodule 可以用在跟其他團隊一起合作開發時候,我們只需要知道大的 git,一些細部的 sub module 就可以讓其他團隊在繼續往下開,相當方便。另外也避免每當要更新檔案的時候,還需要重複 clone 加上 cp 資料到對應目錄。 Reference: Git Submodule 的認識與正確使用! , Pro git: git submodule

Supplement
Git 常見問題 - Git Submodule 指定 Branch

2020年1月12日 星期日

[NodeJS 文章收集] 單元測試:Mocha、Chai 和 Sinon

Source From Here
Mocha
Mocha 是一個 JavaScript 的測試框架,目的是用來管理測試的程式碼。在這裡指定介面(Interface)為 BDD。若使用 BDD 則提供 describe()it()before()after()beforeEach() 與 afterEach() 方法。

語法說明
* describe():描述場景或圈出特定區塊,例如:標明測試的功能或 function。
* it():撰寫測試案例(Test Case)。
* before():在所有測試開始前會執行的程式碼區塊。
* after():在所有測試結束後會執行的程式碼區塊。
* beforeEach():在每個 Test Case 開始前執行的程式碼區塊。
* afterEach():在每個 Test Case 結束後執行的程式碼區塊。

語法範例
  1. describe('hooks', function() {  
  2.   // 測試區塊  
  3.   before(function() {  
  4.     // 在所有測試開始前會執行的程式碼區塊  
  5.   });  
  6.   
  7.   after(function() {  
  8.     // 在所有測試結束後會執行的程式碼區塊  
  9.   });  
  10.   
  11.   beforeEach(function() {  
  12.     // 在每個 Test Case 開始前執行的程式碼區塊  
  13.   });  
  14.   
  15.   afterEach(function() {  
  16.     // 在每個 Test Case 結束後執行的程式碼區塊  
  17.   });  
  18.   
  19.   // 撰寫個別 Test Case  
  20.   it('should ...', function() {  
  21.     // 執行 Test Case  
  22.   });  
  23. });  
Chai
Chai 提供 BDD 語法測試用的斷言庫(Assertion Library)。斷言庫是一種判斷工具,驗證執行結果是否符合預期,若實際結果和預測不同,就是測到 bug 了。以下分 Assert 和 Expect / Should 說明。

Assert
assert(expression, message):測試這個項目的 expression 'foo' === 'bar' 是否為真,若為假則顯示錯誤訊息 message:
test/test_assert1.js
  1. var { assert } = require("chai")  
  2.   
  3. describe('AssertTest', function() {  
  4.     var foo = 'Hello';  
  5.     var bar = "World";  
  6.   
  7.     it('should be equal', function() {  
  8.         assert(foo === bar, 'foo is not bar');  
  9.     });  
  10. });  


Expect / Should
預期 3 等於(===)2。這是使用可串連的 getters 來完成斷言。這些可串聯的 getters 有 to、is、have 等。它很像英文,用很口語的方式做判斷:
test/test_expect1.js
  1. const { expect } = require('chai')  
  2.   
  3. describe('ExpectTest', function(){  
  4.     it('should be equal', function(){  
  5.         expect(3).to.equal(2);  
  6.     })  
  7. });  


測試 add(兩數相加)和 sub(兩數相減)功能. 首先來看被測試模組:
op.js
  1. // op.js module  
  2. function _add(x, y) {  
  3.   return x + y;  
  4. }  
  5.   
  6. function _sub(x, y) {  
  7.   return x - y;  
  8. }  
  9.   
  10. module.exports = {  
  11.   add: _add,  
  12.   sub: _sub,  
  13. };  
接著是測試程式:
test/test_op.js
  1. const { expect } = require('chai')  
  2. const testModule = require('../op');  
  3.   
  4. describe('Test add', () => {  
  5.   it('1 + 2 = 3', () => {  
  6.     expect(testModule.add(12)).to.equal(3);  
  7.   });  
  8.   
  9.   it('3 + 4 = 7', () => {  
  10.     expect(testModule.add(34)).to.equal(7);  
  11.   });  
  12. });  
  13.   
  14. describe('Test sub', () => {  
  15.   it('1 - 2 = -1', () => {  
  16.     expect(testModule.sub(12)).to.equal(-1);  
  17.   });  
  18.   
  19.   it('11 - 4 = 7', () => {  
  20.     expect(testModule.sub(114)).to.equal(7);  
  21.   });  
  22. });  


比較 Assert、Expect、Should 的差異
三者基本上都可完成相同工作,除了
* Should 會修改 Object.prototype
* Should 在瀏覽器環境下,對 IE 有相容問題
* Should 無法客製化錯誤訊息

Sinon
用來產生 Test Double(測試替身),可當成假資料來看,分為 Spy、Stub 和 Mock。

Spy (Spies - Sinon.JS)
對 function call 蒐集資訊,便於對測試結果做驗證。sinon.spy() 會回傳一個 Spy 物件,這個 Spy 物件像蜜糖般包裹於原 function 外,讓我們可以像 function 一樣呼叫。而這個 Spy 物件的 property 會協助蒐集由 function call 得到的資訊,例如:取得第一次呼叫所輸入的參數、該 function 被呼叫的次數。Spy 是三者最簡單的部份,並且 Stub 和 Mock 是建構於 Spy 之上的。

底下為待測程式:
cmp.js
  1. function _comparePeople(people1, people2, callback){  
  2.     let rst = people1.localeCompare(people2)  
  3.     callback(null, rst)  
  4. }  
  5.   
  6. module.exports = {  
  7.     comparePeople: _comparePeople  
  8. }  
接著是測試程式:
test/test_spy.js
  1. const expect = require('chai').expect;  
  2. const sinon = require('sinon');  
  3. const testModule = require('../cmp');  
  4.   
  5. describe('Test comparePeople', function(){  
  6.     it('should call the callback function', function(){  
  7.         var nameList = ['Nina''Ricky'];  
  8.         var callback = sinon.spy();  
  9.         testModule.comparePeople(nameList[0], nameList[0], callback);  
  10.         console.log(callback.callCount); // The calling time  
  11.         expect(callback.callCount).to.equal(1)  
  12.     });  
  13. });  
執行測試:
# npm test -- -g 'comparePeople'



Stub (Stubs - Sinon.JS)
取代 function。與 Spy 不同的是,Spy 依然會執行真的 function,但 Stub 並不會。適用於 Ajax 和 Timer。
What are stubs?
Test stubs are functions (spies) with pre-programmed behavior.

They support the full test spy API in addition to methods which can be used to alter the stub’s behavior.

As spies, stubs can be either anonymous, or wrap existing functions. When wrapping an existing function with a stub, the original function is not called.

一個使用 stub 的測試範例如下:
test/test_stub1.js
  1. const { expect } = require('chai');  
  2. const sinon = require('sinon');  
  3. const uuid = require('node-uuid');  
  4.   
  5. describe('stub example', function(){  
  6.     it('check length of uuid', ()=> {  
  7.         var stub = sinon.stub(uuid, 'v4');  
  8.         var mockId = stub.v4();  
  9.         expect(mockId.length).to.equal(36);  
  10.         uuid.v4.restore();  
  11.     });  
  12. });  
執行測試如下:
# npm test -- -g 'stub example'



更多的 stub 使用範例如下:
test/test_stub2.js
  1. const sinon = require('sinon');  
  2. const { expect } = require('chai')  
  3.   
  4. class A{  
  5.     constructor(){}  
  6.   
  7.     add(a, b){  
  8.         let sum = a + b;  
  9.         return sum  
  10.     }  
  11. }  
  12.   
  13. describe('test A', ()=>{  
  14.     it('demo usage of stub API', function(){  
  15.         const a = new A();  
  16.   
  17.         // 1) API:callsFake  
  18.         sinon.stub(a, 'add').callsFake(function(a, b){  
  19.             return 0  
  20.         })  
  21.   
  22.         var sum = a.add(12)  
  23.         expect(a.add(12)).to.equal(0)  
  24.   
  25.         // 2) Restore add  
  26.         a.add.restore()  
  27.         expect(a.add(12)).to.equal(3)  
  28.   
  29.         // 3) API:withArgs,callThrough  
  30.         var obj = {};  
  31.         obj.sum = function sum(a, b){ return a + b; };  
  32.         sinon.stub(obj, 'sum')  
  33.         obj.sum.withArgs(22).callsFake(function foo(){ return 'bar'; });  
  34.         obj.sum.callThrough();  
  35.         expect(obj.sum(22)).to.equal('bar')  
  36.         expect(obj.sum(12)).to.equal(3)  
  37.         obj.sum.restore()  
  38.         expect(obj.sum(22)).to.equal(4)  
  39.     });  
  40. });  
執行測試如下:


Mock (Mocks - Sinon.JS)
取代整個物件,包含完整實作細節。一個簡單測試範例如下:
test/test_mock.js
  1. const { expect } = require('chai')  
  2. const sinon = require('sinon')  
  3.   
  4. var opts = {  
  5.     call: function(msg){ console.log(msg); }  
  6. };  
  7.   
  8. describe('test Mock', ()=>{  
  9.     it('should pass Hello World to run call()', function(){  
  10.         // 1) Creates a mock for the provided object.  
  11.         var mock = sinon.mock(opts);  
  12.   
  13.         mock  
  14.             .expects('call')  // 2.1 ) Overrides obj.call with a mock function and returns it.  
  15.             .once()  // 2.2) Expect the method to be called exactly once.  
  16.             .withExactArgs('Hello World'); // 2.3) Expect the method to be called with the provided arguments and no others.  
  17.   
  18.         // 3) Launch testing  
  19.         opts.call('Hello World');  
  20.   
  21.         // 4) Restores all mocked methods.  
  22.         mock.restore();  
  23.   
  24.         // 5) Verifies all expectations on the mock.  
  25.         mock.verify();  
  26.     });  
  27. });  


The repo for above sample code.

Supplement
NodeJS 文章收集 - A quick and complete guide to Mocha testing
Chai Assertion Library
FAQ - Why is the constructor invoked despite calling createStubInstance?

[LeetCode] Medium - 227. Basic Calculator II (G,M,A,F)

Source From  Here Question Implement a basic calculator to evaluate a simple expression string. The expression string contains only non-nega...