目录

Go 正确姿势

在编写 Go 代码之时,我跟很多新手一样踩了不少坑,感觉非常有必要将那些踩过的坑记录下来以避免下次犯错。很多人说 Go 简单易学,上手容易,可惜我不是那些很多人中的一个,天资愚钝如此,我只能反复学习了。

常见错误与纠正

assignment to entry in nil map

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package hello

import (
  "testing"
)

/*
=== RUN   TestWrongUsage
--- FAIL: TestWrongUsage (0.00s)
panic: assignment to entry in nil map [recovered]
	panic: assignment to entry in nil map
*/
func TestWrongUsage(t *testing.T) {
  var m map[string]float64
  m["pi"] = 3.1416
  t.Log(m)
}

/*
=== RUN   TestRightUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:39: map[pi:3.1416]
--- PASS: TestRightUsage (0.00s)
PASS
ok  	example.com/hello	0.012s
*/
func TestRightUsage(t *testing.T) {
  m := make(map[string]float64)
  m["pi"] = 3.1416
  t.Log(m)
}

invalid memory address or nil pointer dereference

 1
 2
 3
 4
 5
 6
 7
 8
 9
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
package hello

import (
  "math"
  "testing"
)

type Point struct {
  X, Y float64
}

func (p *Point) Abs() float64 {
  return math.Sqrt(p.X*p.X + p.Y*p.Y)
}

/*
=== RUN   TestWrongUsage
--- FAIL: TestWrongUsage (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x110a643]
*/
func TestWrongUsage(t *testing.T) {
  var p *Point
  t.Log(p.Abs())
}

/*
=== RUN   TestRightUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:26: &{0 0}
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:29: 0
--- PASS: TestRightUsage (0.00s)
PASS
ok  	example.com/hello	0.009s
*/
func TestRightUsage(t *testing.T) {
  var p *Point = new(Point)
  t.Log(p)

  var q Point // has zero value Point{X:0, Y:0}
  t.Log(q.Abs())
}

array won’t change

 1
 2
 3
 4
 5
 6
 7
 8
 9
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
package hello

import (
  "testing"
)

func Foo(a [2]int) {
  a[0] = 8
}

func Bar(a []int) {
  if len(a) > 0 {
    a[0] = 8
  }
}

/*
=== RUN   TestWrongUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:20: [1 2]
--- PASS: TestWrongUsage (0.00s)
PASS
ok  	example.com/hello	0.008s
*/
func TestWrongUsage(t *testing.T) {
  a := [2]int{1, 2}
  Foo(a)
  t.Log(a)
}

/*
=== RUN   TestRightUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:40: [8 2]
--- PASS: TestRightUsage (0.00s)
PASS
ok  	example.com/hello	0.013s
*/
func TestRightUsage(t *testing.T) {
  a := []int{1, 2}
  Bar(a)
  t.Log(a)
}

shadowed variables

 1
 2
 3
 4
 5
 6
 7
 8
 9
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
package hello

import (
	"testing"
)

/*
=== RUN   TestWrongUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:20: 0
--- PASS: TestWrongUsage (0.00s)
PASS
ok  	example.com/hello	0.007s
*/
func TestWrongUsage(t *testing.T) {
  n := 0
  if true {
    n := 1
    n++
  }
  t.Log(n)
}

/*
=== RUN   TestRightUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:36: 2
--- PASS: TestRightUsage (0.00s)
PASS
ok  	example.com/hello	(cached)
*/
func TestRightUsage(t *testing.T) {
  n := 0
  if true {
    n = 1
    n++
  }
  t.Log(n)
}

安装 Go Tools

1
$ go get -u golang.org/x/tools/...

检测隐藏变量:

1
2
3
$ go vet -vettool=$(which shadow) -strict
# example.com/hello
./hello_test.go:17:3: declaration of "n" shadows declaration at line 15

immutable strings

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package hello

import (
  "testing"
)

/*
# example.com/hello [example.com/hello.test]
/Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:16:7: cannot assign to s[0] (strings are immutable)
FAIL	example.com/hello [build failed]
*/
func TestWrongUsage(t *testing.T) {
  s := "hello"
  s[0] = 'H' // IDE 会提示:cannot assign to s[0] (value of type byte)compilerUnassignableOperand
  t.Log(s)
}

/*
=== RUN   TestRightUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:29: Hello
--- PASS: TestRightUsage (0.00s)
PASS
ok  	example.com/hello	0.007s
*/
func TestRightUsage(t *testing.T) {
  buf := []rune("hello")
  buf[0] = 'H'
  s := string(buf)
  t.Log(s)
}

characters add

 1
 2
 3
 4
 5
 6
 7
 8
 9
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
package hello

import (
  "fmt"
  "strconv"
  "testing"
)

/*
=== RUN   TestWrongUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:13: Ta
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:14: 181
--- PASS: TestWrongUsage (0.00s)
PASS
ok  	example.com/hello	0.014s
*/
func TestWrongUsage(t *testing.T) {
  t.Log("T" + "a")
  t.Log('T' + 'a')
}

/*
=== RUN   TestRightUsage
    /Users/xxx/Codes/go/src/github.com/imajinyun/hello/hello_test.go:30: 84a
    /Users/xxx/Codes/go/src/github.com/imajinyun/hello/hello_test.go:31: Ta
--- PASS: TestRightUsage (0.00s)
PASS
ok  	example.com/hello	0.012s
PASS
ok  	example.com/hello	0.012s
*/
func TestRightUsage(t *testing.T) {
  t.Log(strconv.Itoa(84) + string('a'))
  t.Log(fmt.Sprintf("%c%c", 84, 'a'))
}

trim string

 1
 2
 3
 4
 5
 6
 7
 8
 9
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
package hello

import (
  "strings"
  "testing"
)

/*
=== RUN   TestWrongUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:17: true
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:18: true
--- PASS: TestWrongUsage (0.00s)
PASS
ok  	example.com/hello	0.009s
*/
func TestWrongUsage(t *testing.T) {
  t.Log(" hello world " == strings.TrimRight(" hello world ", "hello"))
  t.Log("hello  world" == strings.TrimLeft("hello  world", "world"))
}

/*
=== RUN   TestRightUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:33: true
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:34: true
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:35: true
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:36: true
--- PASS: TestRightUsage (0.00s)
PASS
ok  	example.com/hello	0.008s
*/
func TestRightUsage(t *testing.T) {
  t.Log("hello" == strings.TrimSpace("  hello  "))
  t.Log("hello \t world" == strings.TrimSpace(" \t hello \t world \t "))
  t.Log(" world" == strings.TrimPrefix("hello world", "hello"))
  t.Log("hello " == strings.TrimSuffix("hello world", "world"))
}

elements copy

 1
 2
 3
 4
 5
 6
 7
 8
 9
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
package hello

import (
  "testing"
)

/*
=== RUN   TestWrongUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:18: dst: []
--- PASS: TestWrongUsage (0.00s)
PASS
ok  	example.com/hello	(cached)
*/
func TestWrongUsage(t *testing.T) {
  var src, dst []int
  src = []int{1, 2, 3}
  copy(dst, src) // Copy elements to dst from src.
  t.Log("dst:", dst)
}

/*
=== RUN   TestRightUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:36: dst: [1 2 3] (copied 3 numbers)
--- PASS: TestRightUsage (0.00s)
PASS
ok  	example.com/hello	0.008s
*/
func TestRightUsage(t *testing.T) {
  var src, dst []int
  src = []int{1, 2, 3}
  dst = make([]int, len(src))
  n := copy(dst, src)
  t.Log("dst:", dst, "(copied", n, "numbers)")
}

/*
=== RUN   TestRight2Usage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:40: dst: [1 2 3]
--- PASS: TestRight2Usage (0.00s)
PASS
ok  	example.com/hello	0.016s
*/
func TestRight2Usage(t *testing.T) {
  var src, dst []int
  src = []int{1, 2, 3}
  dst = append(dst, src...)
  t.Log("dst:", dst)
}

can’t change entries in range loop

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package hello

import "testing"

/*
=== RUN   TestWrongUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:21: [1 1 1]
--- PASS: TestWrongUsage (0.00s)
PASS
ok  	example.com/hello	0.011s
*/
func TestWrongUsage(t *testing.T) {
  s := []int{1, 1, 1}
  for _, n := range s {
    n += 1
  }
  t.Log(s)
}

/*
=== RUN   TestRightUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:36: [2 2 2]
--- PASS: TestRightUsage (0.00s)
PASS
ok  	example.com/hello	0.013s
*/
func TestRightUsage(t *testing.T) {
  s := []int{1, 1, 1}
  for i := range s {
    s[i] += 1
  }
  t.Log(s)
}

iteration variable doesn’t see change in range loop

 1
 2
 3
 4
 5
 6
 7
 8
 9
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
package hello

import (
  "fmt"
  "testing"
)

/*
=== RUN   TestWrongUsage
x = 0
x = 0
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:21: [0 8]
--- PASS: TestWrongUsage (0.00s)
PASS
ok  	example.com/hello	0.013s
*/
func TestWrongUsage(t *testing.T) {
  var a [2]int
  for _, x := range a {
    fmt.Println("x =", x)
    a[1] = 8
  }
  t.Log(a)
}

/*
=== RUN   TestRightUsage
x = 0
x = 8
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:39: [0 8]
--- PASS: TestRightUsage (0.00s)
PASS
ok  	example.com/hello	0.009s
*/
func TestRightUsage(t *testing.T) {
  var a [2]int
  for _, x := range a[:] {
    fmt.Println("x =", x)
    a[1] = 8
  }
  t.Log(a)
}

iteration variables and closures

 1
 2
 3
 4
 5
 6
 7
 8
 9
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
package hello

import (
  "sync"
  "testing"
)

/*
=== RUN   TestWrongUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:23: 5
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:23: 5
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:23: 5
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:23: 5
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:23: 5
--- PASS: TestWrongUsage (0.00s)
PASS
ok  	example.com/hello	0.013s
*/
func TestWrongUsage(t *testing.T) {
  var wg sync.WaitGroup
  var n int = 5
  wg.Add(n)
  for i := 0; i < n; i++ {
    go func() {
      t.Log(i)
      wg.Done()
    }()
  }
  wg.Wait()
}

/*
=== RUN   TestRightUsage
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:47: 4
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:47: 1
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:47: 0
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:47: 3
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:47: 2
--- PASS: TestRightUsage (0.00s)
PASS
ok  	example.com/hello	0.009s
*/
func TestRightUsage(t *testing.T) {
  var wg sync.WaitGroup
  var n int = 5
  wg.Add(n)
  for i := 0; i < n; i++ {
    x := i // Create a unique variable for each closure.
    go func() {
      t.Log(x)
      wg.Done()
    }()
  }
  wg.Wait()
}

常用的一些写法

检查字典中是否存在指定键

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package hello

import "testing"

/*
=== RUN   TestIfKeyExistInMap
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:12: value: 100
--- PASS: TestIfKeyExistInMap (0.00s)
PASS
ok  	example.com/hello	0.009s
*/
func TestIfKeyExistInMap(t *testing.T) {
  dict := map[string]int{"foo": 100, "bar": 200}
  value, ok := dict["foo"]
  if ok {
    t.Log("value:", value)
  } else {
    t.Log("Key not found")
  }
}

连接两个切片

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package hello

import "testing"

/*
=== RUN   TestCombineTwoSlice
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:17: [1 2 3 4]
--- PASS: TestCombineTwoSlice (0.00s)
PASS
ok  	example.com/hello	0.007s
*/
func TestCombineTwoSlice(t *testing.T) {
  slice := append([]int{1, 2}, []int{3, 4}...)
  t.Log(slice)
}

将 byte 转换为 string

标准转换:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package hello

import "testing"

/*
=== RUN   TestByteToStr
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:16:
        [72 101 108 108 111 32 87 111 114 108 100]
        Hello World
--- PASS: TestByteToStr (0.00s)
PASS
ok  	example.com/hello	0.007s
*/
func TestByteToStr(t *testing.T) {
  b := []byte("Hello World")
  t.Logf("\n%v\n%v", b, string(b))
}

强制转换:

 1
 2
 3
 4
 5
 6
 7
 8
 9
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
package hello

import (
  "reflect"
  "testing"
  "unsafe"
)

/*
=== RUN   TestByteToString
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:19:
        [72 101 108 108 111 32 87 111 114 108 100]
        "Hello World"
        Hello World
--- PASS: TestByteToString (0.00s)
PASS
ok  	example.com/hello	0.008s
*/
func TestByteToString(t *testing.T) {
  b := []byte{72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100}
  s := byteToString(b)
  t.Logf("\n%v\n%#v\n%v", b, s, s)
}

func byteToString(b []byte) (s string) {
  data := make([]byte, len(b))
  for i, c := range b {
    data[i] = c
  }
  hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
  hdr.Data = uintptr(unsafe.Pointer(&data[0]))
  hdr.Len = len(b)
  return s
}

将 string 转换为 byte

标准转换:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package hello

import "testing"

/*
=== RUN   TestStringToByte
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:18:
        Hello World
        [72 101 108 108 111 32 87 111 114 108 100]
--- PASS: TestStringToByte (0.00s)
PASS
ok  	example.com/hello	0.008s
*/
func TestStringToByte(t *testing.T) {
  s := "Hello World"
  t.Logf("\n%v\n%v", s, []byte(s))
}

强制转换:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package hello

import "testing"

/*
=== RUN   TestStringToByte
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:19:
        Hello World
        [72 101 108 108 111 32 87 111 114 108 100]
--- PASS: TestStringToByte (0.00s)
PASS
ok  	example.com/hello	0.012s
*/
func TestStringToByte(t *testing.T) {
  s := "Hello World"
  b := stringToByte(s)
  t.Logf("\n%v\n%v\n", s, b)
}

func stringToByte(s string) []byte {
  b := make([]byte, len(s))
  for i := 0; i < len(s); i++ {
    c := s[i]
    b[i] = c
  }
  return b
}

将 rune 转换为 string

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package hello

import "testing"

/*
=== RUN   TestRuneToString
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:8:
        r = [72 101 108 108 111 32 87 111 114 108 100 33]
        r = [U+0048 U+0065 U+006C U+006C U+006F U+0020 U+0057 U+006F U+0072 U+006C U+0064 U+0021]
        s = Hello World!
--- PASS: TestRuneToString (0.00s)
PASS
ok  	example.com/hello
*/
func TestRuneToString(t *testing.T) {
  r := []rune{72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33}
  s := string(r)
  t.Logf("\nr = %v\nr = %U\ns = %v", r, r, s)
}

将 string 转换为 rune

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package hello

import "testing"

/*
=== RUN   TestStringToRune
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:14:
        s = Hello World!
        r = [72 101 108 108 111 32 87 111 114 108 100 33]
        r = [U+0048 U+0065 U+006C U+006C U+006F U+0020 U+0057 U+006F U+0072 U+006C U+0064 U+0021]
--- PASS: TestStringToRune (0.00s)
PASS
ok  	example.com/hello	0.016s
*/
func TestStringToRune(t *testing.T) {
  s := "Hello World!"
  r := []rune(s)
  t.Logf("\ns = %v\nr = %v\nr = %U", s, r, r)
}

结构体比较

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package hello

import (
  "reflect"
  "testing"
)

type Devloper struct {
  Name string
  Lang string
  Age  int
}

/*
=== RUN   TestCompareStruct
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:18: false
    /Users/xxx/Codes/go/src/github.com/xxx/hello/hello_test.go:19: true
--- PASS: TestCompareStruct (0.00s)
PASS
ok  	example.com/hello
*/
func TestCompareStruct(t *testing.T) {
  d1 := Devloper{"Foo", "Java", 26}
  d2 := Devloper{"Bar", "Go", 28}
  d3 := Devloper{"Bar", "Go", 28}
  t.Log(reflect.DeepEqual(d1, d2))
  t.Log(reflect.DeepEqual(d2, d3))
}

How to Properly Hash and Salt Passwords in Golang Using Bcrypt

Download the golang bcrypt library using go get golang.org/x/crypto/bcrypt.

功能代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package example

import (
	"golang.org/x/crypto/bcrypt"
)

// Hash password using the bcrypt hashing algorithm.
func genHashPassword(password string) (string, error) {
	// Convert password string to byte slice.
	var passwordBytes = []byte(password)

	// Hash password with bcrypt's min cost.
	hashedPasswordBytes, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost)

	return string(hashedPasswordBytes), err
}

// Check if two passwords match using Bcrypt's CompareHashAndPassword
// which return nil on success and an error on failure.
func cmpHashPassword(hashedPassword, currPassword string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(currPassword))

	return err == nil
}

测试代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
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
package example

import (
	"testing"
)

func TestHashPassword(t *testing.T) {
	var hashPassword, err = genHashPassword("mypassword")

	if err != nil {
		t.Error("Failed to hash password")
		return
	}
	t.Logf("Get hashed password: %v", hashPassword)
}

func TestCmpHashPassword(t *testing.T) {
	var currPassword = "mypassword"
	var hashPassword, err = genHashPassword(currPassword)
	if err != nil {
		t.Error("Failed to hash password")
		return
	}
	t.Logf("Get hashed password: %v", hashPassword)
	t.Logf("Cmp hashed password: %v", cmpHashPassword(hashPassword, currPassword))
}
/*
Running tool: /opt/homebrew/bin/go test -timeout 30s -coverprofile=/var/folders/nk/dtkbhx993b57y5kt49l4qsb40000gn/T/vscode-goOPwBHl/go-code-cover example -v

=== RUN   TestHashPassword
    /Users/xxx/Codes/github.com/xxx/inotes/vendor/acme_test.go:14: Get hashed password: $2a$10$L/tY5of7u9FOw94y7PrKR.3irKHtBattazT/JzypTcg5e25Kzu3vG
--- PASS: TestHashPassword (0.07s)
=== RUN   TestCmpHashPassword
    /Users/xxx/Codes/github.com/xxx/inotes/vendor/acme_test.go:24: Get hashed password: $2a$10$b2fKxBL0FQm0/EOE6nLFe.h6X/Xc367jrqwLyTGonS5Ot78AG6V62
    /Users/xxx/Codes/github.com/xxx/inotes/vendor/acme_test.go:25: Cmp hashed password: true
--- PASS: TestCmpHashPassword (0.14s)
PASS
coverage: 100.0% of statements
ok  	example	0.531s	coverage: 100.0% of statements

*/