Skip to content

Commit 45d419f

Browse files
authored
Merge pull request #5 for v0.4 Release
2 parents c8b0b5a + 5efd287 commit 45d419f

File tree

6 files changed

+267
-14
lines changed

6 files changed

+267
-14
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ branches:
1010
- /^v[0-9]\.[0-9]/
1111

1212
go:
13-
- 1.6
14-
- 1.7
13+
- 1.7.x
1514
- 1.8
15+
- 1.8.x
1616
- tip
1717

1818
go_import_path: aahframework.org/essentials.v0

README.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# essentials - aah framework
2-
32
[![Build Status](https://travis-ci.org/go-aah/essentials.svg?branch=master)](https://travis-ci.org/go-aah/essentials) [![codecov](https://codecov.io/gh/go-aah/essentials/branch/master/graph/badge.svg)](https://codecov.io/gh/go-aah/essentials/branch/master) [![Go Report Card](https://goreportcard.com/badge/aahframework.org/essentials.v0)](https://goreportcard.com/report/aahframework.org/essentials.v0)
4-
[![Version](https://img.shields.io/badge/version-0.3-blue.svg)](https://github.com/go-aah/essentials/releases/latest) [![GoDoc](https://godoc.org/aahframework.org/essentials.v0?status.svg)](https://godoc.org/aahframework.org/essentials.v0) [![License](https://img.shields.io/github/license/go-aah/essentials.svg)](LICENSE)
3+
[![Version](https://img.shields.io/badge/version-0.4-blue.svg)](https://github.com/go-aah/essentials/releases/latest) [![GoDoc](https://godoc.org/aahframework.org/essentials.v0?status.svg)](https://godoc.org/aahframework.org/essentials.v0) [![License](https://img.shields.io/github/license/go-aah/essentials.svg)](LICENSE)
54

6-
***v0.3 [released](https://github.com/go-aah/essentials/releases/latest) and tagged on Mar 16, 2017***
5+
***v0.4 [released](https://github.com/go-aah/essentials/releases/latest) and tagged on Mar 30, 2017***
76

8-
`essentials` contain simple & useful utils for go lang. aah framework utilizes essentials (aka `ess`) library across. Essential library complements with handy methods for:
7+
`essentials` contains simple & useful utils methods for Go. aah framework utilizes essentials (aka `ess`) library across. Essentials library complements with handy methods, refer godoc to know more about methods:
98
* filepath
9+
* GUID (Globally Unique Identifier)
1010
* go
1111
* io
1212
* os
@@ -23,10 +23,4 @@
2323
go get -u aahframework.org/essentials.v0
2424
```
2525

26-
#### Development Version - Edge
27-
```sh
28-
# install the development version
29-
go get -u aahframework.org/essentials.v0-unstable
30-
```
31-
3226
See official page [TODO]

essentials.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
// essentials library across. It's pretty handy you can use it too :)
77
package ess
88

9-
// Version no. of go-aah/essentials library
10-
var Version = "0.3"
9+
// Version no. of essentials library
10+
var Version = "0.4"

filepath_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"io/ioutil"
99
"os"
1010
"runtime"
11+
"strings"
1112
"testing"
1213

1314
"aahframework.org/test.v0/assert"
@@ -250,3 +251,37 @@ func TestDirPaths(t *testing.T) {
250251
assert.True(t, IsSliceContainsString(dirs, path12))
251252
assert.False(t, IsSliceContainsString(dirs, join(path22, "not-exists")))
252253
}
254+
255+
func TestFilesPath(t *testing.T) {
256+
testdataPath := getTestdataPath()
257+
path1 := join(testdataPath, "dirpaths", "level1", "level2", "level3")
258+
path11 := join(testdataPath, "dirpaths", "level1", "level1-1")
259+
path12 := join(testdataPath, "dirpaths", "level1", "level1-2")
260+
path21 := join(testdataPath, "dirpaths", "level1", "level2", "level2-1")
261+
path22 := join(testdataPath, "dirpaths", "level1", "level2", "level2-2")
262+
defer DeleteFiles(join(testdataPath, "dirpaths"))
263+
264+
_ = MkDirAll(path1, 0755)
265+
_ = MkDirAll(path11, 0755)
266+
_ = MkDirAll(path12, 0755)
267+
_ = MkDirAll(path21, 0755)
268+
_ = MkDirAll(path22, 0755)
269+
270+
_ = ioutil.WriteFile(join(path1, "file1.txt"), []byte("file1.txt"), 0600)
271+
_ = ioutil.WriteFile(join(path11, "file11.txt"), []byte("file11.txt"), 0600)
272+
_ = ioutil.WriteFile(join(path12, "file12.txt"), []byte("file12.txt"), 0600)
273+
_ = ioutil.WriteFile(join(path21, "file21.txt"), []byte("file21.txt"), 0600)
274+
_ = ioutil.WriteFile(join(path22, "file22.txt"), []byte("file22.txt"), 0600)
275+
276+
files, err := FilesPath(join(testdataPath, "dirpaths"), true)
277+
assert.Nil(t, err)
278+
assert.True(t, strings.HasSuffix(files[0], "file11.txt"))
279+
assert.True(t, strings.HasSuffix(files[1], "file12.txt"))
280+
assert.True(t, strings.HasSuffix(files[2], "file21.txt"))
281+
assert.True(t, strings.HasSuffix(files[3], "file22.txt"))
282+
assert.True(t, strings.HasSuffix(files[4], "file1.txt"))
283+
284+
files, err = FilesPath(path11, false)
285+
assert.Nil(t, err)
286+
assert.True(t, strings.HasSuffix(files[0], "file11.txt"))
287+
}

guid.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) Jeevanandam M. (https://github.com/jeevatkm)
2+
// go-aah/essentials source code and usage is governed by a MIT style
3+
// license that can be found in the LICENSE file.
4+
5+
package ess
6+
7+
import (
8+
"crypto/md5"
9+
"crypto/rand"
10+
"encoding/binary"
11+
"encoding/hex"
12+
"errors"
13+
"io"
14+
"os"
15+
"sync/atomic"
16+
"time"
17+
)
18+
19+
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
20+
// GUID generation
21+
// Code inspired from mgo/bson ObjectId
22+
//___________________________________
23+
24+
var (
25+
// guidCounter is atomically incremented when generating a new GUID
26+
// using UniqueID() function. It's used as a counter part of an id.
27+
guidCounter = readRandomUint32()
28+
29+
// machineID stores machine id generated once and used in subsequent calls
30+
// to UniqueId function.
31+
machineID = readMachineID()
32+
33+
// processID is current Process Id
34+
processID = os.Getpid()
35+
)
36+
37+
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
38+
// Global methods
39+
//___________________________________
40+
41+
// NewGUID method returns a new Globally Unique identifier (GUID).
42+
//
43+
// The 12-byte `UniqueId` consists of-
44+
// - 4-byte value representing the seconds since the Unix epoch,
45+
// - 3-byte machine identifier,
46+
// - 2-byte process id, and
47+
// - 3-byte counter, starting with a random value.
48+
//
49+
// NewGUID generation using Mongo Object ID algorithm to generate globally
50+
// unique ids - https://docs.mongodb.com/manual/reference/method/ObjectId/
51+
func NewGUID() string {
52+
var b [12]byte
53+
// Timestamp, 4 bytes, big endian
54+
binary.BigEndian.PutUint32(b[:], uint32(time.Now().Unix()))
55+
56+
// Machine, first 3 bytes of md5(hostname)
57+
b[4], b[5], b[6] = machineID[0], machineID[1], machineID[2]
58+
59+
// Pid, 2 bytes, specs don't specify endianness, but we use big endian.
60+
b[7], b[8] = byte(processID>>8), byte(processID)
61+
62+
// Increment, 3 bytes, big endian
63+
i := atomic.AddUint32(&guidCounter, 1)
64+
b[9], b[10], b[11] = byte(i>>16), byte(i>>8), byte(i)
65+
66+
return hex.EncodeToString(b[:])
67+
}
68+
69+
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
70+
// Unexported methods
71+
//___________________________________
72+
73+
// readRandomUint32 returns a random guidCounter.
74+
func readRandomUint32() uint32 {
75+
var b [4]byte
76+
if _, err := io.ReadFull(rand.Reader, b[:]); err == nil {
77+
return (uint32(b[0]) << 0) | (uint32(b[1]) << 8) | (uint32(b[2]) << 16) | (uint32(b[3]) << 24)
78+
}
79+
80+
panic(errors.New("ess - guid: unable to generate random object id"))
81+
}
82+
83+
// readMachineID generates and returns a machine id.
84+
// If this function fails to get the hostname it will cause a runtime error.
85+
func readMachineID() []byte {
86+
var sum [3]byte
87+
id := sum[:]
88+
89+
if hostname, err := os.Hostname(); err == nil {
90+
hw := md5.New()
91+
_, _ = hw.Write([]byte(hostname))
92+
copy(id, hw.Sum(nil))
93+
return id
94+
}
95+
96+
if _, err := io.ReadFull(rand.Reader, id); err == nil {
97+
return id
98+
}
99+
100+
// return nil, errors.New("guid: unable to get hostname and random bytes")
101+
panic(errors.New("ess - guid: unable to get hostname and random bytes"))
102+
}

guid_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright (c) Jeevanandam M. (https://github.com/jeevatkm)
2+
// go-aah/essentials source code and usage is governed by a MIT style
3+
// license that can be found in the LICENSE file.
4+
5+
package ess
6+
7+
import (
8+
"encoding/binary"
9+
"encoding/hex"
10+
"testing"
11+
"time"
12+
13+
"aahframework.org/test.v0/assert"
14+
)
15+
16+
func TestGUIDNew(t *testing.T) {
17+
// Generate 10 ids
18+
ids := make([]string, 10)
19+
for i := 0; i < 10; i++ {
20+
ids[i] = NewGUID()
21+
}
22+
for i := 1; i < 10; i++ {
23+
prevID := ids[i-1]
24+
id := ids[i]
25+
// Test for uniqueness among all other 9 generated ids
26+
for j, tid := range ids {
27+
if j != i {
28+
assert.NotEqualf(t, id, tid, "Generated ID is not unique")
29+
}
30+
}
31+
32+
// Check that timestamp was incremented and is within 30 seconds of the previous one
33+
secs := getTime(id).Sub(getTime(prevID)).Seconds()
34+
assert.Equalf(t, (secs >= 0 && secs <= 30), true, "wrong timestamp in generated Id")
35+
36+
// Check that machine ids are the same
37+
assert.Equal(t, getMachine(id), getMachine(prevID))
38+
39+
// Check that pids are the same
40+
assert.Equal(t, getPid(id), getPid(prevID))
41+
42+
// Test for proper increment
43+
delta := int(getCounter(id) - getCounter(prevID))
44+
assert.Equalf(t, delta, 1, "wrong increment in generated Id")
45+
}
46+
}
47+
48+
type guidParts struct {
49+
id string
50+
timestamp int64
51+
machine []byte
52+
pid uint16
53+
counter int32
54+
}
55+
56+
var uniqueIds = []guidParts{
57+
guidParts{
58+
"4d88e15b60f486e428412dc9",
59+
1300816219,
60+
[]byte{0x60, 0xf4, 0x86},
61+
0xe428,
62+
4271561,
63+
},
64+
guidParts{
65+
"000000000000000000000000",
66+
0,
67+
[]byte{0x00, 0x00, 0x00},
68+
0x0000,
69+
0,
70+
},
71+
guidParts{
72+
"00000000aabbccddee000001",
73+
0,
74+
[]byte{0xaa, 0xbb, 0xcc},
75+
0xddee,
76+
1,
77+
},
78+
}
79+
80+
func TestGUIDPartsExtraction(t *testing.T) {
81+
for i, v := range uniqueIds {
82+
assert.Equalf(t, getTime(v.id), time.Unix(v.timestamp, 0), "#%d timestamp", i)
83+
assert.Equalf(t, getMachine(v.id), v.machine, "#%d machine", i)
84+
assert.Equalf(t, getPid(v.id), v.pid, "#%d pid", i)
85+
assert.Equalf(t, getCounter(v.id), v.counter, "#%d counter", i)
86+
}
87+
}
88+
89+
func BenchmarkNewGUID(b *testing.B) {
90+
b.RunParallel(func(pb *testing.PB) {
91+
for pb.Next() {
92+
_ = NewGUID()
93+
}
94+
})
95+
}
96+
97+
func getMachine(id string) []byte {
98+
return byteSlice(id, 4, 7)
99+
}
100+
101+
func getTime(id string) time.Time {
102+
secs := int64(binary.BigEndian.Uint32(byteSlice(id, 0, 4)))
103+
return time.Unix(secs, 0)
104+
}
105+
106+
func getPid(id string) uint16 {
107+
return binary.BigEndian.Uint16(byteSlice(id, 7, 9))
108+
}
109+
110+
func getCounter(id string) int32 {
111+
b := byteSlice(id, 9, 12)
112+
// Counter is stored as big-endian 3-byte value
113+
return int32(uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2]))
114+
}
115+
116+
func byteSlice(id string, s, e int) []byte {
117+
if len(id) == 24 {
118+
b, _ := hex.DecodeString(id)
119+
return b[s:e]
120+
}
121+
return nil
122+
}

0 commit comments

Comments
 (0)