Add FlipKick log events transformer

This commit is contained in:
Elizabeth
2018-08-07 10:51:34 -05:00
committed by GitHub
parent 66decaaa78
commit c617cd9c9d
1066 changed files with 200929 additions and 95 deletions
+9
View File
@@ -0,0 +1,9 @@
language: go
go:
- 1.4.3
- 1.5.3
- tip
script:
- go test -v ./...
+10
View File
@@ -0,0 +1,10 @@
# How to contribute
We definitely welcome patches and contribution to this project!
### Legal requirements
In order to protect both you and ourselves, you will need to sign the
[Contributor License Agreement](https://cla.developers.google.com/clas).
You may have already signed it for other Google projects.
+1
View File
@@ -0,0 +1 @@
Paul Borman <borman@google.com>
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) 2009,2014 Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+13
View File
@@ -0,0 +1,13 @@
This project was automatically exported from code.google.com/p/go-uuid
# uuid ![build status](https://travis-ci.org/pborman/uuid.svg?branch=master)
The uuid package generates and inspects UUIDs based on [RFC 4122](http://tools.ietf.org/html/rfc4122) and DCE 1.1: Authentication and Security Services.
###### Install
`go get github.com/pborman/uuid`
###### Documentation
[![GoDoc](https://godoc.org/github.com/pborman/uuid?status.svg)](http://godoc.org/github.com/pborman/uuid)
Full `go doc` style documentation for the package can be viewed online without installing this package by using the GoDoc site here:
http://godoc.org/github.com/pborman/uuid
+84
View File
@@ -0,0 +1,84 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
"fmt"
"os"
)
// A Domain represents a Version 2 domain
type Domain byte
// Domain constants for DCE Security (Version 2) UUIDs.
const (
Person = Domain(0)
Group = Domain(1)
Org = Domain(2)
)
// NewDCESecurity returns a DCE Security (Version 2) UUID.
//
// The domain should be one of Person, Group or Org.
// On a POSIX system the id should be the users UID for the Person
// domain and the users GID for the Group. The meaning of id for
// the domain Org or on non-POSIX systems is site defined.
//
// For a given domain/id pair the same token may be returned for up to
// 7 minutes and 10 seconds.
func NewDCESecurity(domain Domain, id uint32) UUID {
uuid := NewUUID()
if uuid != nil {
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
uuid[9] = byte(domain)
binary.BigEndian.PutUint32(uuid[0:], id)
}
return uuid
}
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
// domain with the id returned by os.Getuid.
//
// NewDCEPerson(Person, uint32(os.Getuid()))
func NewDCEPerson() UUID {
return NewDCESecurity(Person, uint32(os.Getuid()))
}
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
// domain with the id returned by os.Getgid.
//
// NewDCEGroup(Group, uint32(os.Getgid()))
func NewDCEGroup() UUID {
return NewDCESecurity(Group, uint32(os.Getgid()))
}
// Domain returns the domain for a Version 2 UUID or false.
func (uuid UUID) Domain() (Domain, bool) {
if v, _ := uuid.Version(); v != 2 {
return 0, false
}
return Domain(uuid[9]), true
}
// Id returns the id for a Version 2 UUID or false.
func (uuid UUID) Id() (uint32, bool) {
if v, _ := uuid.Version(); v != 2 {
return 0, false
}
return binary.BigEndian.Uint32(uuid[0:4]), true
}
func (d Domain) String() string {
switch d {
case Person:
return "Person"
case Group:
return "Group"
case Org:
return "Org"
}
return fmt.Sprintf("Domain%d", int(d))
}
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The uuid package generates and inspects UUIDs.
//
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security Services.
package uuid
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"crypto/md5"
"crypto/sha1"
"hash"
)
// Well known Name Space IDs and UUIDs
var (
NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
NIL = Parse("00000000-0000-0000-0000-000000000000")
)
// NewHash returns a new UUID derived from the hash of space concatenated with
// data generated by h. The hash should be at least 16 byte in length. The
// first 16 bytes of the hash are used to form the UUID. The version of the
// UUID will be the lower 4 bits of version. NewHash is used to implement
// NewMD5 and NewSHA1.
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
h.Reset()
h.Write(space)
h.Write([]byte(data))
s := h.Sum(nil)
uuid := make([]byte, 16)
copy(uuid, s)
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
return uuid
}
// NewMD5 returns a new MD5 (Version 3) UUID based on the
// supplied name space and data.
//
// NewHash(md5.New(), space, data, 3)
func NewMD5(space UUID, data []byte) UUID {
return NewHash(md5.New(), space, data, 3)
}
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
// supplied name space and data.
//
// NewHash(sha1.New(), space, data, 5)
func NewSHA1(space UUID, data []byte) UUID {
return NewHash(sha1.New(), space, data, 5)
}
+83
View File
@@ -0,0 +1,83 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"errors"
"fmt"
)
// MarshalText implements encoding.TextMarshaler.
func (u UUID) MarshalText() ([]byte, error) {
if len(u) != 16 {
return nil, nil
}
var js [36]byte
encodeHex(js[:], u)
return js[:], nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (u *UUID) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
id := Parse(string(data))
if id == nil {
return errors.New("invalid UUID")
}
*u = id
return nil
}
// MarshalBinary implements encoding.BinaryMarshaler.
func (u UUID) MarshalBinary() ([]byte, error) {
return u[:], nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
func (u *UUID) UnmarshalBinary(data []byte) error {
if len(data) == 0 {
return nil
}
if len(data) != 16 {
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
}
var id [16]byte
copy(id[:], data)
*u = id[:]
return nil
}
// MarshalText implements encoding.TextMarshaler.
func (u Array) MarshalText() ([]byte, error) {
var js [36]byte
encodeHex(js[:], u[:])
return js[:], nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (u *Array) UnmarshalText(data []byte) error {
id := Parse(string(data))
if id == nil {
return errors.New("invalid UUID")
}
*u = id.Array()
return nil
}
// MarshalBinary implements encoding.BinaryMarshaler.
func (u Array) MarshalBinary() ([]byte, error) {
return u[:], nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
func (u *Array) UnmarshalBinary(data []byte) error {
if len(data) != 16 {
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
}
copy(u[:], data)
return nil
}
+124
View File
@@ -0,0 +1,124 @@
// Copyright 2014 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"encoding/json"
"reflect"
"testing"
)
var testUUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
var testArray = testUUID.Array()
func TestJSON(t *testing.T) {
type S struct {
ID1 UUID
ID2 UUID
}
s1 := S{ID1: testUUID}
data, err := json.Marshal(&s1)
if err != nil {
t.Fatal(err)
}
var s2 S
if err := json.Unmarshal(data, &s2); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(&s1, &s2) {
t.Errorf("got %#v, want %#v", s2, s1)
}
}
func TestJSONArray(t *testing.T) {
type S struct {
ID1 Array
ID2 Array
}
s1 := S{ID1: testArray}
data, err := json.Marshal(&s1)
if err != nil {
t.Fatal(err)
}
var s2 S
if err := json.Unmarshal(data, &s2); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(&s1, &s2) {
t.Errorf("got %#v, want %#v", s2, s1)
}
}
func TestMarshal(t *testing.T) {
data, err := testUUID.MarshalBinary()
if err != nil {
t.Fatalf("MarhsalBinary returned unexpected error %v", err)
}
if !bytes.Equal(data, testUUID) {
t.Fatalf("MarhsalBinary returns %x, want %x", data, testUUID)
}
var u UUID
u.UnmarshalBinary(data)
if !Equal(data, u) {
t.Fatalf("UnmarhsalBinary returns %v, want %v", u, testUUID)
}
}
func TestMarshalArray(t *testing.T) {
data, err := testArray.MarshalBinary()
if err != nil {
t.Fatalf("MarhsalBinary returned unexpected error %v", err)
}
if !bytes.Equal(data, testUUID) {
t.Fatalf("MarhsalBinary returns %x, want %x", data, testUUID)
}
var a Array
a.UnmarshalBinary(data)
if a != testArray {
t.Fatalf("UnmarhsalBinary returns %v, want %v", a, testArray)
}
}
func TestMarshalTextArray(t *testing.T) {
data, err := testArray.MarshalText()
if err != nil {
t.Fatalf("MarhsalText returned unexpected error %v", err)
}
var a Array
a.UnmarshalText(data)
if a != testArray {
t.Fatalf("UnmarhsalText returns %v, want %v", a, testArray)
}
}
func BenchmarkUUID_MarshalJSON(b *testing.B) {
x := &struct {
UUID UUID `json:"uuid"`
}{}
x.UUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if x.UUID == nil {
b.Fatal("invalid uuid")
}
for i := 0; i < b.N; i++ {
js, err := json.Marshal(x)
if err != nil {
b.Fatalf("marshal json: %#v (%v)", js, err)
}
}
}
func BenchmarkUUID_UnmarshalJSON(b *testing.B) {
js := []byte(`{"uuid":"f47ac10b-58cc-0372-8567-0e02b2c3d479"}`)
var x *struct {
UUID UUID `json:"uuid"`
}
for i := 0; i < b.N; i++ {
err := json.Unmarshal(js, &x)
if err != nil {
b.Fatalf("marshal json: %#v (%v)", js, err)
}
}
}
+117
View File
@@ -0,0 +1,117 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"net"
"sync"
)
var (
nodeMu sync.Mutex
interfaces []net.Interface // cached list of interfaces
ifname string // name of interface being used
nodeID []byte // hardware for version 1 UUIDs
)
// NodeInterface returns the name of the interface from which the NodeID was
// derived. The interface "user" is returned if the NodeID was set by
// SetNodeID.
func NodeInterface() string {
defer nodeMu.Unlock()
nodeMu.Lock()
return ifname
}
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
// If name is "" then the first usable interface found will be used or a random
// Node ID will be generated. If a named interface cannot be found then false
// is returned.
//
// SetNodeInterface never fails when name is "".
func SetNodeInterface(name string) bool {
defer nodeMu.Unlock()
nodeMu.Lock()
return setNodeInterface(name)
}
func setNodeInterface(name string) bool {
if interfaces == nil {
var err error
interfaces, err = net.Interfaces()
if err != nil && name != "" {
return false
}
}
for _, ifs := range interfaces {
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
if setNodeID(ifs.HardwareAddr) {
ifname = ifs.Name
return true
}
}
}
// We found no interfaces with a valid hardware address. If name
// does not specify a specific interface generate a random Node ID
// (section 4.1.6)
if name == "" {
if nodeID == nil {
nodeID = make([]byte, 6)
}
randomBits(nodeID)
return true
}
return false
}
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
// if not already set.
func NodeID() []byte {
defer nodeMu.Unlock()
nodeMu.Lock()
if nodeID == nil {
setNodeInterface("")
}
nid := make([]byte, 6)
copy(nid, nodeID)
return nid
}
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
// of id are used. If id is less than 6 bytes then false is returned and the
// Node ID is not set.
func SetNodeID(id []byte) bool {
defer nodeMu.Unlock()
nodeMu.Lock()
if setNodeID(id) {
ifname = "user"
return true
}
return false
}
func setNodeID(id []byte) bool {
if len(id) < 6 {
return false
}
if nodeID == nil {
nodeID = make([]byte, 6)
}
copy(nodeID, id)
return true
}
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) NodeID() []byte {
if len(uuid) != 16 {
return nil
}
node := make([]byte, 6)
copy(node, uuid[10:])
return node
}
+66
View File
@@ -0,0 +1,66 @@
// Copyright 2014 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"flag"
"runtime"
"testing"
"time"
)
// This test is only run when --regressions is passed on the go test line.
var regressions = flag.Bool("regressions", false, "run uuid regression tests")
// TestClockSeqRace tests for a particular race condition of returning two
// identical Version1 UUIDs. The duration of 1 minute was chosen as the race
// condition, before being fixed, nearly always occured in under 30 seconds.
func TestClockSeqRace(t *testing.T) {
if !*regressions {
t.Skip("skipping regression tests")
}
duration := time.Minute
done := make(chan struct{})
defer close(done)
ch := make(chan UUID, 10000)
ncpu := runtime.NumCPU()
switch ncpu {
case 0, 1:
// We can't run the test effectively.
t.Skip("skipping race test, only one CPU detected")
return
default:
runtime.GOMAXPROCS(ncpu)
}
for i := 0; i < ncpu; i++ {
go func() {
for {
select {
case <-done:
return
case ch <- NewUUID():
}
}
}()
}
uuids := make(map[string]bool)
cnt := 0
start := time.Now()
for u := range ch {
s := u.String()
if uuids[s] {
t.Errorf("duplicate uuid after %d in %v: %s", cnt, time.Since(start), s)
return
}
uuids[s] = true
if time.Since(start) > duration {
return
}
cnt++
}
}
+66
View File
@@ -0,0 +1,66 @@
// Copyright 2015 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"database/sql/driver"
"errors"
"fmt"
)
// Scan implements sql.Scanner so UUIDs can be read from databases transparently
// Currently, database types that map to string and []byte are supported. Please
// consult database-specific driver documentation for matching types.
func (uuid *UUID) Scan(src interface{}) error {
switch src.(type) {
case string:
// if an empty UUID comes from a table, we return a null UUID
if src.(string) == "" {
return nil
}
// see uuid.Parse for required string format
parsed := Parse(src.(string))
if parsed == nil {
return errors.New("Scan: invalid UUID format")
}
*uuid = parsed
case []byte:
b := src.([]byte)
// if an empty UUID comes from a table, we return a null UUID
if len(b) == 0 {
return nil
}
// assumes a simple slice of bytes if 16 bytes
// otherwise attempts to parse
if len(b) == 16 {
*uuid = UUID(b)
} else {
u := Parse(string(b))
if u == nil {
return errors.New("Scan: invalid UUID format")
}
*uuid = u
}
default:
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
}
return nil
}
// Value implements sql.Valuer so that UUIDs can be written to databases
// transparently. Currently, UUIDs map to strings. Please consult
// database-specific driver documentation for matching types.
func (uuid UUID) Value() (driver.Value, error) {
return uuid.String(), nil
}
+96
View File
@@ -0,0 +1,96 @@
// Copyright 2015 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"strings"
"testing"
)
func TestScan(t *testing.T) {
var stringTest string = "f47ac10b-58cc-0372-8567-0e02b2c3d479"
var byteTest []byte = Parse(stringTest)
var badTypeTest int = 6
var invalidTest string = "f47ac10b-58cc-0372-8567-0e02b2c3d4"
// sunny day tests
var uuid UUID
err := (&uuid).Scan(stringTest)
if err != nil {
t.Fatal(err)
}
err = (&uuid).Scan([]byte(stringTest))
if err != nil {
t.Fatal(err)
}
err = (&uuid).Scan(byteTest)
if err != nil {
t.Fatal(err)
}
// bad type tests
err = (&uuid).Scan(badTypeTest)
if err == nil {
t.Error("int correctly parsed and shouldn't have")
}
if !strings.Contains(err.Error(), "unable to scan type") {
t.Error("attempting to parse an int returned an incorrect error message")
}
// invalid/incomplete uuids
err = (&uuid).Scan(invalidTest)
if err == nil {
t.Error("invalid uuid was parsed without error")
}
if !strings.Contains(err.Error(), "invalid UUID") {
t.Error("attempting to parse an invalid UUID returned an incorrect error message")
}
err = (&uuid).Scan(byteTest[:len(byteTest)-2])
if err == nil {
t.Error("invalid byte uuid was parsed without error")
}
if !strings.Contains(err.Error(), "invalid UUID") {
t.Error("attempting to parse an invalid byte UUID returned an incorrect error message")
}
// empty tests
uuid = nil
var emptySlice []byte
err = (&uuid).Scan(emptySlice)
if err != nil {
t.Fatal(err)
}
if uuid != nil {
t.Error("UUID was not nil after scanning empty byte slice")
}
uuid = nil
var emptyString string
err = (&uuid).Scan(emptyString)
if err != nil {
t.Fatal(err)
}
if uuid != nil {
t.Error("UUID was not nil after scanning empty string")
}
}
func TestValue(t *testing.T) {
stringTest := "f47ac10b-58cc-0372-8567-0e02b2c3d479"
uuid := Parse(stringTest)
val, _ := uuid.Value()
if val != stringTest {
t.Error("Value() did not return expected string")
}
}
+132
View File
@@ -0,0 +1,132 @@
// Copyright 2014 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
"sync"
"time"
)
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
// 1582.
type Time int64
const (
lillian = 2299160 // Julian day of 15 Oct 1582
unix = 2440587 // Julian day of 1 Jan 1970
epoch = unix - lillian // Days between epochs
g1582 = epoch * 86400 // seconds between epochs
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
)
var (
timeMu sync.Mutex
lasttime uint64 // last time we returned
clock_seq uint16 // clock sequence for this run
timeNow = time.Now // for testing
)
// UnixTime converts t the number of seconds and nanoseconds using the Unix
// epoch of 1 Jan 1970.
func (t Time) UnixTime() (sec, nsec int64) {
sec = int64(t - g1582ns100)
nsec = (sec % 10000000) * 100
sec /= 10000000
return sec, nsec
}
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
// clock sequence as well as adjusting the clock sequence as needed. An error
// is returned if the current time cannot be determined.
func GetTime() (Time, uint16, error) {
defer timeMu.Unlock()
timeMu.Lock()
return getTime()
}
func getTime() (Time, uint16, error) {
t := timeNow()
// If we don't have a clock sequence already, set one.
if clock_seq == 0 {
setClockSequence(-1)
}
now := uint64(t.UnixNano()/100) + g1582ns100
// If time has gone backwards with this clock sequence then we
// increment the clock sequence
if now <= lasttime {
clock_seq = ((clock_seq + 1) & 0x3fff) | 0x8000
}
lasttime = now
return Time(now), clock_seq, nil
}
// ClockSequence returns the current clock sequence, generating one if not
// already set. The clock sequence is only used for Version 1 UUIDs.
//
// The uuid package does not use global static storage for the clock sequence or
// the last time a UUID was generated. Unless SetClockSequence a new random
// clock sequence is generated the first time a clock sequence is requested by
// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated
// for
func ClockSequence() int {
defer timeMu.Unlock()
timeMu.Lock()
return clockSequence()
}
func clockSequence() int {
if clock_seq == 0 {
setClockSequence(-1)
}
return int(clock_seq & 0x3fff)
}
// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to
// -1 causes a new sequence to be generated.
func SetClockSequence(seq int) {
defer timeMu.Unlock()
timeMu.Lock()
setClockSequence(seq)
}
func setClockSequence(seq int) {
if seq == -1 {
var b [2]byte
randomBits(b[:]) // clock sequence
seq = int(b[0])<<8 | int(b[1])
}
old_seq := clock_seq
clock_seq = uint16(seq&0x3fff) | 0x8000 // Set our variant
if old_seq != clock_seq {
lasttime = 0
}
}
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
// uuid. It returns false if uuid is not valid. The time is only well defined
// for version 1 and 2 UUIDs.
func (uuid UUID) Time() (Time, bool) {
if len(uuid) != 16 {
return 0, false
}
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
return Time(time), true
}
// ClockSequence returns the clock sequence encoded in uuid. It returns false
// if uuid is not valid. The clock sequence is only well defined for version 1
// and 2 UUIDs.
func (uuid UUID) ClockSequence() (int, bool) {
if len(uuid) != 16 {
return 0, false
}
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true
}
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"io"
)
// randomBits completely fills slice b with random data.
func randomBits(b []byte) {
if _, err := io.ReadFull(rander, b); err != nil {
panic(err.Error()) // rand should never fail
}
}
// xvalues returns the value of a byte as a hexadecimal digit or 255.
var xvalues = [256]byte{
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
}
// xtob converts the the first two hex bytes of x into a byte.
func xtob(x string) (byte, bool) {
b1 := xvalues[x[0]]
b2 := xvalues[x[1]]
return (b1 << 4) | b2, b1 != 255 && b2 != 255
}
+201
View File
@@ -0,0 +1,201 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"strings"
)
// Array is a pass-by-value UUID that can be used as an effecient key in a map.
type Array [16]byte
// UUID converts uuid into a slice.
func (uuid Array) UUID() UUID {
return uuid[:]
}
// String returns the string representation of uuid,
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
func (uuid Array) String() string {
return uuid.UUID().String()
}
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
// 4122.
type UUID []byte
// A Version represents a UUIDs version.
type Version byte
// A Variant represents a UUIDs variant.
type Variant byte
// Constants returned by Variant.
const (
Invalid = Variant(iota) // Invalid UUID
RFC4122 // The variant specified in RFC4122
Reserved // Reserved, NCS backward compatibility.
Microsoft // Reserved, Microsoft Corporation backward compatibility.
Future // Reserved for future definition.
)
var rander = rand.Reader // random function
// New returns a new random (version 4) UUID as a string. It is a convenience
// function for NewRandom().String().
func New() string {
return NewRandom().String()
}
// Parse decodes s into a UUID or returns nil. Both the UUID form of
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
func Parse(s string) UUID {
if len(s) == 36+9 {
if strings.ToLower(s[:9]) != "urn:uuid:" {
return nil
}
s = s[9:]
} else if len(s) != 36 {
return nil
}
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return nil
}
var uuid [16]byte
for i, x := range [16]int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
if v, ok := xtob(s[x:]); !ok {
return nil
} else {
uuid[i] = v
}
}
return uuid[:]
}
// Equal returns true if uuid1 and uuid2 are equal.
func Equal(uuid1, uuid2 UUID) bool {
return bytes.Equal(uuid1, uuid2)
}
// Array returns an array representation of uuid that can be used as a map key.
// Array panics if uuid is not valid.
func (uuid UUID) Array() Array {
if len(uuid) != 16 {
panic("invalid uuid")
}
var a Array
copy(a[:], uuid)
return a
}
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// , or "" if uuid is invalid.
func (uuid UUID) String() string {
if len(uuid) != 16 {
return ""
}
var buf [36]byte
encodeHex(buf[:], uuid)
return string(buf[:])
}
// URN returns the RFC 2141 URN form of uuid,
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
func (uuid UUID) URN() string {
if len(uuid) != 16 {
return ""
}
var buf [36 + 9]byte
copy(buf[:], "urn:uuid:")
encodeHex(buf[9:], uuid)
return string(buf[:])
}
func encodeHex(dst []byte, uuid UUID) {
hex.Encode(dst[:], uuid[:4])
dst[8] = '-'
hex.Encode(dst[9:13], uuid[4:6])
dst[13] = '-'
hex.Encode(dst[14:18], uuid[6:8])
dst[18] = '-'
hex.Encode(dst[19:23], uuid[8:10])
dst[23] = '-'
hex.Encode(dst[24:], uuid[10:])
}
// Variant returns the variant encoded in uuid. It returns Invalid if
// uuid is invalid.
func (uuid UUID) Variant() Variant {
if len(uuid) != 16 {
return Invalid
}
switch {
case (uuid[8] & 0xc0) == 0x80:
return RFC4122
case (uuid[8] & 0xe0) == 0xc0:
return Microsoft
case (uuid[8] & 0xe0) == 0xe0:
return Future
default:
return Reserved
}
}
// Version returns the version of uuid. It returns false if uuid is not
// valid.
func (uuid UUID) Version() (Version, bool) {
if len(uuid) != 16 {
return 0, false
}
return Version(uuid[6] >> 4), true
}
func (v Version) String() string {
if v > 15 {
return fmt.Sprintf("BAD_VERSION_%d", v)
}
return fmt.Sprintf("VERSION_%d", v)
}
func (v Variant) String() string {
switch v {
case RFC4122:
return "RFC4122"
case Reserved:
return "Reserved"
case Microsoft:
return "Microsoft"
case Future:
return "Future"
case Invalid:
return "Invalid"
}
return fmt.Sprintf("BadVariant%d", int(v))
}
// SetRand sets the random number generator to r, which implements io.Reader.
// If r.Read returns an error when the package requests random data then
// a panic will be issued.
//
// Calling SetRand with nil sets the random number generator to the default
// generator.
func SetRand(r io.Reader) {
if r == nil {
rander = rand.Reader
return
}
rander = r
}
+543
View File
@@ -0,0 +1,543 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"fmt"
"os"
"strings"
"testing"
"time"
)
type test struct {
in string
version Version
variant Variant
isuuid bool
}
var tests = []test{
{"f47ac10b-58cc-0372-8567-0e02b2c3d479", 0, RFC4122, true},
{"f47ac10b-58cc-1372-8567-0e02b2c3d479", 1, RFC4122, true},
{"f47ac10b-58cc-2372-8567-0e02b2c3d479", 2, RFC4122, true},
{"f47ac10b-58cc-3372-8567-0e02b2c3d479", 3, RFC4122, true},
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-5372-8567-0e02b2c3d479", 5, RFC4122, true},
{"f47ac10b-58cc-6372-8567-0e02b2c3d479", 6, RFC4122, true},
{"f47ac10b-58cc-7372-8567-0e02b2c3d479", 7, RFC4122, true},
{"f47ac10b-58cc-8372-8567-0e02b2c3d479", 8, RFC4122, true},
{"f47ac10b-58cc-9372-8567-0e02b2c3d479", 9, RFC4122, true},
{"f47ac10b-58cc-a372-8567-0e02b2c3d479", 10, RFC4122, true},
{"f47ac10b-58cc-b372-8567-0e02b2c3d479", 11, RFC4122, true},
{"f47ac10b-58cc-c372-8567-0e02b2c3d479", 12, RFC4122, true},
{"f47ac10b-58cc-d372-8567-0e02b2c3d479", 13, RFC4122, true},
{"f47ac10b-58cc-e372-8567-0e02b2c3d479", 14, RFC4122, true},
{"f47ac10b-58cc-f372-8567-0e02b2c3d479", 15, RFC4122, true},
{"urn:uuid:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
{"URN:UUID:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-1567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-2567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-3567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-4567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-5567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-6567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-7567-0e02b2c3d479", 4, Reserved, true},
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-4372-9567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-4372-a567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-4372-b567-0e02b2c3d479", 4, RFC4122, true},
{"f47ac10b-58cc-4372-c567-0e02b2c3d479", 4, Microsoft, true},
{"f47ac10b-58cc-4372-d567-0e02b2c3d479", 4, Microsoft, true},
{"f47ac10b-58cc-4372-e567-0e02b2c3d479", 4, Future, true},
{"f47ac10b-58cc-4372-f567-0e02b2c3d479", 4, Future, true},
{"f47ac10b158cc-5372-a567-0e02b2c3d479", 0, Invalid, false},
{"f47ac10b-58cc25372-a567-0e02b2c3d479", 0, Invalid, false},
{"f47ac10b-58cc-53723a567-0e02b2c3d479", 0, Invalid, false},
{"f47ac10b-58cc-5372-a56740e02b2c3d479", 0, Invalid, false},
{"f47ac10b-58cc-5372-a567-0e02-2c3d479", 0, Invalid, false},
{"g47ac10b-58cc-4372-a567-0e02b2c3d479", 0, Invalid, false},
}
var constants = []struct {
c interface{}
name string
}{
{Person, "Person"},
{Group, "Group"},
{Org, "Org"},
{Invalid, "Invalid"},
{RFC4122, "RFC4122"},
{Reserved, "Reserved"},
{Microsoft, "Microsoft"},
{Future, "Future"},
{Domain(17), "Domain17"},
{Variant(42), "BadVariant42"},
}
func testTest(t *testing.T, in string, tt test) {
uuid := Parse(in)
if ok := (uuid != nil); ok != tt.isuuid {
t.Errorf("Parse(%s) got %v expected %v\b", in, ok, tt.isuuid)
}
if uuid == nil {
return
}
if v := uuid.Variant(); v != tt.variant {
t.Errorf("Variant(%s) got %d expected %d\b", in, v, tt.variant)
}
if v, _ := uuid.Version(); v != tt.version {
t.Errorf("Version(%s) got %d expected %d\b", in, v, tt.version)
}
}
func TestUUID(t *testing.T) {
for _, tt := range tests {
testTest(t, tt.in, tt)
testTest(t, strings.ToUpper(tt.in), tt)
}
}
func TestConstants(t *testing.T) {
for x, tt := range constants {
v, ok := tt.c.(fmt.Stringer)
if !ok {
t.Errorf("%x: %v: not a stringer", x, v)
} else if s := v.String(); s != tt.name {
v, _ := tt.c.(int)
t.Errorf("%x: Constant %T:%d gives %q, expected %q", x, tt.c, v, s, tt.name)
}
}
}
func TestRandomUUID(t *testing.T) {
m := make(map[string]bool)
for x := 1; x < 32; x++ {
uuid := NewRandom()
s := uuid.String()
if m[s] {
t.Errorf("NewRandom returned duplicated UUID %s", s)
}
m[s] = true
if v, _ := uuid.Version(); v != 4 {
t.Errorf("Random UUID of version %s", v)
}
if uuid.Variant() != RFC4122 {
t.Errorf("Random UUID is variant %d", uuid.Variant())
}
}
}
func TestNew(t *testing.T) {
m := make(map[string]bool)
for x := 1; x < 32; x++ {
s := New()
if m[s] {
t.Errorf("New returned duplicated UUID %s", s)
}
m[s] = true
uuid := Parse(s)
if uuid == nil {
t.Errorf("New returned %q which does not decode", s)
continue
}
if v, _ := uuid.Version(); v != 4 {
t.Errorf("Random UUID of version %s", v)
}
if uuid.Variant() != RFC4122 {
t.Errorf("Random UUID is variant %d", uuid.Variant())
}
}
}
func clockSeq(t *testing.T, uuid UUID) int {
seq, ok := uuid.ClockSequence()
if !ok {
t.Fatalf("%s: invalid clock sequence", uuid)
}
return seq
}
func TestClockSeq(t *testing.T) {
// Fake time.Now for this test to return a monotonically advancing time; restore it at end.
defer func(orig func() time.Time) { timeNow = orig }(timeNow)
monTime := time.Now()
timeNow = func() time.Time {
monTime = monTime.Add(1 * time.Second)
return monTime
}
SetClockSequence(-1)
uuid1 := NewUUID()
uuid2 := NewUUID()
if clockSeq(t, uuid1) != clockSeq(t, uuid2) {
t.Errorf("clock sequence %d != %d", clockSeq(t, uuid1), clockSeq(t, uuid2))
}
SetClockSequence(-1)
uuid2 = NewUUID()
// Just on the very off chance we generated the same sequence
// two times we try again.
if clockSeq(t, uuid1) == clockSeq(t, uuid2) {
SetClockSequence(-1)
uuid2 = NewUUID()
}
if clockSeq(t, uuid1) == clockSeq(t, uuid2) {
t.Errorf("Duplicate clock sequence %d", clockSeq(t, uuid1))
}
SetClockSequence(0x1234)
uuid1 = NewUUID()
if seq := clockSeq(t, uuid1); seq != 0x1234 {
t.Errorf("%s: expected seq 0x1234 got 0x%04x", uuid1, seq)
}
}
func TestCoding(t *testing.T) {
text := "7d444840-9dc0-11d1-b245-5ffdce74fad2"
urn := "urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2"
data := UUID{
0x7d, 0x44, 0x48, 0x40,
0x9d, 0xc0,
0x11, 0xd1,
0xb2, 0x45,
0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2,
}
if v := data.String(); v != text {
t.Errorf("%x: encoded to %s, expected %s", data, v, text)
}
if v := data.URN(); v != urn {
t.Errorf("%x: urn is %s, expected %s", data, v, urn)
}
uuid := Parse(text)
if !Equal(uuid, data) {
t.Errorf("%s: decoded to %s, expected %s", text, uuid, data)
}
}
func TestVersion1(t *testing.T) {
uuid1 := NewUUID()
uuid2 := NewUUID()
if Equal(uuid1, uuid2) {
t.Errorf("%s:duplicate uuid", uuid1)
}
if v, _ := uuid1.Version(); v != 1 {
t.Errorf("%s: version %s expected 1", uuid1, v)
}
if v, _ := uuid2.Version(); v != 1 {
t.Errorf("%s: version %s expected 1", uuid2, v)
}
n1 := uuid1.NodeID()
n2 := uuid2.NodeID()
if !bytes.Equal(n1, n2) {
t.Errorf("Different nodes %x != %x", n1, n2)
}
t1, ok := uuid1.Time()
if !ok {
t.Errorf("%s: invalid time", uuid1)
}
t2, ok := uuid2.Time()
if !ok {
t.Errorf("%s: invalid time", uuid2)
}
q1, ok := uuid1.ClockSequence()
if !ok {
t.Errorf("%s: invalid clock sequence", uuid1)
}
q2, ok := uuid2.ClockSequence()
if !ok {
t.Errorf("%s: invalid clock sequence", uuid2)
}
switch {
case t1 == t2 && q1 == q2:
t.Error("time stopped")
case t1 > t2 && q1 == q2:
t.Error("time reversed")
case t1 < t2 && q1 != q2:
t.Error("clock sequence chaned unexpectedly")
}
}
func TestNode(t *testing.T) {
// This test is mostly to make sure we don't leave nodeMu locked.
ifname = ""
if ni := NodeInterface(); ni != "" {
t.Errorf("NodeInterface got %q, want %q", ni, "")
}
if SetNodeInterface("xyzzy") {
t.Error("SetNodeInterface succeeded on a bad interface name")
}
if !SetNodeInterface("") {
t.Error("SetNodeInterface failed")
}
if ni := NodeInterface(); ni == "" {
t.Error("NodeInterface returned an empty string")
}
ni := NodeID()
if len(ni) != 6 {
t.Errorf("ni got %d bytes, want 6", len(ni))
}
hasData := false
for _, b := range ni {
if b != 0 {
hasData = true
}
}
if !hasData {
t.Error("nodeid is all zeros")
}
id := []byte{1, 2, 3, 4, 5, 6, 7, 8}
SetNodeID(id)
ni = NodeID()
if !bytes.Equal(ni, id[:6]) {
t.Errorf("got nodeid %v, want %v", ni, id[:6])
}
if ni := NodeInterface(); ni != "user" {
t.Errorf("got inteface %q, want %q", ni, "user")
}
}
func TestNodeAndTime(t *testing.T) {
// Time is February 5, 1998 12:30:23.136364800 AM GMT
uuid := Parse("7d444840-9dc0-11d1-b245-5ffdce74fad2")
node := []byte{0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2}
ts, ok := uuid.Time()
if ok {
c := time.Unix(ts.UnixTime())
want := time.Date(1998, 2, 5, 0, 30, 23, 136364800, time.UTC)
if !c.Equal(want) {
t.Errorf("Got time %v, want %v", c, want)
}
} else {
t.Errorf("%s: bad time", uuid)
}
if !bytes.Equal(node, uuid.NodeID()) {
t.Errorf("Expected node %v got %v", node, uuid.NodeID())
}
}
func TestMD5(t *testing.T) {
uuid := NewMD5(NameSpace_DNS, []byte("python.org")).String()
want := "6fa459ea-ee8a-3ca4-894e-db77e160355e"
if uuid != want {
t.Errorf("MD5: got %q expected %q", uuid, want)
}
}
func TestSHA1(t *testing.T) {
uuid := NewSHA1(NameSpace_DNS, []byte("python.org")).String()
want := "886313e1-3b8a-5372-9b90-0c9aee199e5d"
if uuid != want {
t.Errorf("SHA1: got %q expected %q", uuid, want)
}
}
func TestNodeID(t *testing.T) {
nid := []byte{1, 2, 3, 4, 5, 6}
SetNodeInterface("")
s := NodeInterface()
if s == "" || s == "user" {
t.Errorf("NodeInterface %q after SetInteface", s)
}
node1 := NodeID()
if node1 == nil {
t.Error("NodeID nil after SetNodeInterface", s)
}
SetNodeID(nid)
s = NodeInterface()
if s != "user" {
t.Errorf("Expected NodeInterface %q got %q", "user", s)
}
node2 := NodeID()
if node2 == nil {
t.Error("NodeID nil after SetNodeID", s)
}
if bytes.Equal(node1, node2) {
t.Error("NodeID not changed after SetNodeID", s)
} else if !bytes.Equal(nid, node2) {
t.Errorf("NodeID is %x, expected %x", node2, nid)
}
}
func testDCE(t *testing.T, name string, uuid UUID, domain Domain, id uint32) {
if uuid == nil {
t.Errorf("%s failed", name)
return
}
if v, _ := uuid.Version(); v != 2 {
t.Errorf("%s: %s: expected version 2, got %s", name, uuid, v)
return
}
if v, ok := uuid.Domain(); !ok || v != domain {
if !ok {
t.Errorf("%s: %d: Domain failed", name, uuid)
} else {
t.Errorf("%s: %s: expected domain %d, got %d", name, uuid, domain, v)
}
}
if v, ok := uuid.Id(); !ok || v != id {
if !ok {
t.Errorf("%s: %d: Id failed", name, uuid)
} else {
t.Errorf("%s: %s: expected id %d, got %d", name, uuid, id, v)
}
}
}
func TestDCE(t *testing.T) {
testDCE(t, "NewDCESecurity", NewDCESecurity(42, 12345678), 42, 12345678)
testDCE(t, "NewDCEPerson", NewDCEPerson(), Person, uint32(os.Getuid()))
testDCE(t, "NewDCEGroup", NewDCEGroup(), Group, uint32(os.Getgid()))
}
type badRand struct{}
func (r badRand) Read(buf []byte) (int, error) {
for i, _ := range buf {
buf[i] = byte(i)
}
return len(buf), nil
}
func TestBadRand(t *testing.T) {
SetRand(badRand{})
uuid1 := New()
uuid2 := New()
if uuid1 != uuid2 {
t.Errorf("expected duplicates, got %q and %q", uuid1, uuid2)
}
SetRand(nil)
uuid1 = New()
uuid2 = New()
if uuid1 == uuid2 {
t.Errorf("unexpected duplicates, got %q", uuid1)
}
}
func TestUUID_Array(t *testing.T) {
expect := Array{
0xf4, 0x7a, 0xc1, 0x0b,
0x58, 0xcc,
0x03, 0x72,
0x85, 0x67,
0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79,
}
uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if uuid == nil {
t.Fatal("invalid uuid")
}
if uuid.Array() != expect {
t.Fatal("invalid array")
}
}
func TestArray_UUID(t *testing.T) {
array := Array{
0xf4, 0x7a, 0xc1, 0x0b,
0x58, 0xcc,
0x03, 0x72,
0x85, 0x67,
0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79,
}
expect := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if expect == nil {
t.Fatal("invalid uuid")
}
if !bytes.Equal(array.UUID(), expect) {
t.Fatal("invalid uuid")
}
}
func BenchmarkParse(b *testing.B) {
for i := 0; i < b.N; i++ {
uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if uuid == nil {
b.Fatal("invalid uuid")
}
}
}
func BenchmarkNew(b *testing.B) {
for i := 0; i < b.N; i++ {
New()
}
}
func BenchmarkUUID_String(b *testing.B) {
uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if uuid == nil {
b.Fatal("invalid uuid")
}
for i := 0; i < b.N; i++ {
if uuid.String() == "" {
b.Fatal("invalid uuid")
}
}
}
func BenchmarkUUID_URN(b *testing.B) {
uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if uuid == nil {
b.Fatal("invalid uuid")
}
for i := 0; i < b.N; i++ {
if uuid.URN() == "" {
b.Fatal("invalid uuid")
}
}
}
func BenchmarkUUID_Array(b *testing.B) {
expect := Array{
0xf4, 0x7a, 0xc1, 0x0b,
0x58, 0xcc,
0x03, 0x72,
0x85, 0x67,
0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79,
}
uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if uuid == nil {
b.Fatal("invalid uuid")
}
for i := 0; i < b.N; i++ {
if uuid.Array() != expect {
b.Fatal("invalid array")
}
}
}
func BenchmarkArray_UUID(b *testing.B) {
array := Array{
0xf4, 0x7a, 0xc1, 0x0b,
0x58, 0xcc,
0x03, 0x72,
0x85, 0x67,
0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79,
}
expect := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
if expect == nil {
b.Fatal("invalid uuid")
}
for i := 0; i < b.N; i++ {
if !bytes.Equal(array.UUID(), expect) {
b.Fatal("invalid uuid")
}
}
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
)
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
// sequence, and the current time. If the NodeID has not been set by SetNodeID
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
// be set NewUUID returns nil. If clock sequence has not been set by
// SetClockSequence then it will be set automatically. If GetTime fails to
// return the current NewUUID returns nil.
func NewUUID() UUID {
if nodeID == nil {
SetNodeInterface("")
}
now, seq, err := GetTime()
if err != nil {
return nil
}
uuid := make([]byte, 16)
time_low := uint32(now & 0xffffffff)
time_mid := uint16((now >> 32) & 0xffff)
time_hi := uint16((now >> 48) & 0x0fff)
time_hi |= 0x1000 // Version 1
binary.BigEndian.PutUint32(uuid[0:], time_low)
binary.BigEndian.PutUint16(uuid[4:], time_mid)
binary.BigEndian.PutUint16(uuid[6:], time_hi)
binary.BigEndian.PutUint16(uuid[8:], seq)
copy(uuid[10:], nodeID)
return uuid
}
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
// Random returns a Random (Version 4) UUID or panics.
//
// The strength of the UUIDs is based on the strength of the crypto/rand
// package.
//
// A note about uniqueness derived from from the UUID Wikipedia entry:
//
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 1011),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() UUID {
uuid := make([]byte, 16)
randomBits([]byte(uuid))
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid
}
+92
View File
@@ -0,0 +1,92 @@
# Created by https://www.gitignore.io
### OSX ###
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Windows ###
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### Linux ###
*~
# KDE directory preferences
.directory
### Go ###
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
### vim ###
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
*~
### JetBrains files ###
.idea/
*.iml
+31
View File
@@ -0,0 +1,31 @@
language: go
go:
- 1.7.5
- tip
os:
- linux
- osx
matrix:
include:
- os: osx
go: 1.7.5
env:
- GOFLAGS="-tags kqueue"
allow_failures:
- go: tip
env:
global:
- GOBIN=$HOME/bin
- PATH=$HOME/bin:$PATH
install:
- go get -t -v ./...
script:
- "(go version | grep -q 1.4) || go tool vet -all ."
- go install $GOFLAGS ./...
- go test -v -timeout 60s -race $GOFLAGS ./...
+10
View File
@@ -0,0 +1,10 @@
# List of individuals who contributed to the Notify package.
#
# The up-to-date list of the authors one may obtain with:
#
# ~ $ git shortlog -es | cut -f2 | rev | uniq -f1 | rev
#
Pawel Blaszczyk <blaszczykpb@gmail.com>
Pawel Knap <pawelknap88@gmail.com>
Rafal Jeczalik <rjeczalik@gmail.com>
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2015 The Notify Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+22
View File
@@ -0,0 +1,22 @@
notify [![GoDoc](https://godoc.org/github.com/rjeczalik/notify?status.svg)](https://godoc.org/github.com/rjeczalik/notify) [![Build Status](https://img.shields.io/travis/rjeczalik/notify/master.svg)](https://travis-ci.org/rjeczalik/notify "inotify + FSEvents + kqueue") [![Build status](https://img.shields.io/appveyor/ci/rjeczalik/notify-246.svg)](https://ci.appveyor.com/project/rjeczalik/notify-246 "ReadDirectoryChangesW") [![Coverage Status](https://img.shields.io/coveralls/rjeczalik/notify/master.svg)](https://coveralls.io/r/rjeczalik/notify?branch=master)
======
Filesystem event notification library on steroids. (under active development)
*Documentation*
[godoc.org/github.com/rjeczalik/notify](https://godoc.org/github.com/rjeczalik/notify)
*Installation*
```
~ $ go get -u github.com/rjeczalik/notify
```
*Projects using notify*
- [github.com/rjeczalik/cmd/notify](https://godoc.org/github.com/rjeczalik/cmd/notify)
- [github.com/cortesi/devd](https://github.com/cortesi/devd)
- [github.com/cortesi/modd](https://github.com/cortesi/modd)
- [github.com/syncthing/syncthing-inotify](https://github.com/syncthing/syncthing-inotify)
- [github.com/OrlovEvgeny/TinyJPG](https://github.com/OrlovEvgeny/TinyJPG)
+23
View File
@@ -0,0 +1,23 @@
version: "{build}"
os: Windows Server 2012 R2
clone_folder: c:\projects\src\github.com\rjeczalik\notify
environment:
PATH: c:\projects\bin;%PATH%
GOPATH: c:\projects
NOTIFY_TIMEOUT: 5s
install:
- go version
- go get -v -t ./...
build_script:
- go tool vet -all .
- go build ./...
- go test -v -timeout 60s -race ./...
test: off
deploy: off
+53
View File
@@ -0,0 +1,53 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
import (
"log"
"os"
"runtime"
"strings"
)
var dbgprint func(...interface{})
var dbgprintf func(string, ...interface{})
var dbgcallstack func(max int) []string
func init() {
if _, ok := os.LookupEnv("NOTIFY_DEBUG"); ok || debugTag {
log.SetOutput(os.Stdout)
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)
dbgprint = func(v ...interface{}) {
v = append([]interface{}{"[D] "}, v...)
log.Println(v...)
}
dbgprintf = func(format string, v ...interface{}) {
format = "[D] " + format
log.Printf(format, v...)
}
dbgcallstack = func(max int) []string {
pc, stack := make([]uintptr, max), make([]string, 0, max)
runtime.Callers(2, pc)
for _, pc := range pc {
if f := runtime.FuncForPC(pc); f != nil {
fname := f.Name()
idx := strings.LastIndex(fname, string(os.PathSeparator))
if idx != -1 {
stack = append(stack, fname[idx+1:])
} else {
stack = append(stack, fname)
}
}
}
return stack
}
return
}
dbgprint = func(v ...interface{}) {}
dbgprintf = func(format string, v ...interface{}) {}
dbgcallstack = func(max int) []string { return nil }
}
+9
View File
@@ -0,0 +1,9 @@
// Copyright (c) 2014-2018 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build debug
package notify
var debugTag = true
+9
View File
@@ -0,0 +1,9 @@
// Copyright (c) 2014-2018 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build !debug
package notify
var debugTag = false
+43
View File
@@ -0,0 +1,43 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// Package notify implements access to filesystem events.
//
// Notify is a high-level abstraction over filesystem watchers like inotify,
// kqueue, FSEvents, FEN or ReadDirectoryChangesW. Watcher implementations are
// split into two groups: ones that natively support recursive notifications
// (FSEvents and ReadDirectoryChangesW) and ones that do not (inotify, kqueue, FEN).
// For more details see watcher and recursiveWatcher interfaces in watcher.go
// source file.
//
// On top of filesystem watchers notify maintains a watchpoint tree, which provides
// a strategy for creating and closing filesystem watches and dispatching filesystem
// events to user channels.
//
// An event set is just an event list joint using bitwise OR operator
// into a single event value.
// Both the platform-independent (see Constants) and specific events can be used.
// Refer to the event_*.go source files for information about the available
// events.
//
// A filesystem watch or just a watch is platform-specific entity which represents
// a single path registered for notifications for specific event set. Setting a watch
// means using platform-specific API calls for creating / initializing said watch.
// For each watcher the API call is:
//
// - FSEvents: FSEventStreamCreate
// - inotify: notify_add_watch
// - kqueue: kevent
// - ReadDirectoryChangesW: CreateFile+ReadDirectoryChangesW
// - FEN: port_get
//
// To rewatch means to either shrink or expand an event set that was previously
// registered during watch operation for particular filesystem watch.
//
// A watchpoint is a list of user channel and event set pairs for particular
// path (watchpoint tree's node). A single watchpoint can contain multiple
// different user channels registered to listen for one or more events. A single
// user channel can be registered in one or more watchpoints, recursive and
// non-recursive ones as well.
package notify
+143
View File
@@ -0,0 +1,143 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
import (
"fmt"
"strings"
)
// Event represents the type of filesystem action.
//
// Number of available event values is dependent on the target system or the
// watcher implmenetation used (e.g. it's possible to use either kqueue or
// FSEvents on Darwin).
//
// Please consult documentation for your target platform to see list of all
// available events.
type Event uint32
// Create, Remove, Write and Rename are the only event values guaranteed to be
// present on all platforms.
const (
Create = osSpecificCreate
Remove = osSpecificRemove
Write = osSpecificWrite
Rename = osSpecificRename
// All is handful alias for all platform-independent event values.
All = Create | Remove | Write | Rename
)
const internal = recursive | omit
// String implements fmt.Stringer interface.
func (e Event) String() string {
var s []string
for _, strmap := range []map[Event]string{estr, osestr} {
for ev, str := range strmap {
if e&ev == ev {
s = append(s, str)
}
}
}
return strings.Join(s, "|")
}
// EventInfo describes an event reported by the underlying filesystem notification
// subsystem.
//
// It always describes single event, even if the OS reported a coalesced action.
// Reported path is absolute and clean.
//
// For non-recursive watchpoints its base is always equal to the path passed
// to corresponding Watch call.
//
// The value of Sys if system-dependent and can be nil.
//
// Sys
//
// Under Darwin (FSEvents) Sys() always returns a non-nil *notify.FSEvent value,
// which is defined as:
//
// type FSEvent struct {
// Path string // real path of the file or directory
// ID uint64 // ID of the event (FSEventStreamEventId)
// Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags)
// }
//
// For possible values of Flags see Darwin godoc for notify or FSEvents
// documentation for FSEventStreamEventFlags constants:
//
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html#//apple_ref/doc/constant_group/FSEventStreamEventFlags
//
// Under Linux (inotify) Sys() always returns a non-nil *unix.InotifyEvent
// value, defined as:
//
// type InotifyEvent struct {
// Wd int32 // Watch descriptor
// Mask uint32 // Mask describing event
// Cookie uint32 // Unique cookie associating related events (for rename(2))
// Len uint32 // Size of name field
// Name [0]uint8 // Optional null-terminated name
// }
//
// More information about inotify masks and the usage of inotify_event structure
// can be found at:
//
// http://man7.org/linux/man-pages/man7/inotify.7.html
//
// Under Darwin, DragonFlyBSD, FreeBSD, NetBSD, OpenBSD (kqueue) Sys() always
// returns a non-nil *notify.Kevent value, which is defined as:
//
// type Kevent struct {
// Kevent *syscall.Kevent_t // Kevent is a kqueue specific structure
// FI os.FileInfo // FI describes file/dir
// }
//
// More information about syscall.Kevent_t can be found at:
//
// https://www.freebsd.org/cgi/man.cgi?query=kqueue
//
// Under Windows (ReadDirectoryChangesW) Sys() always returns nil. The documentation
// of watcher's WinAPI function can be found at:
//
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465%28v=vs.85%29.aspx
type EventInfo interface {
Event() Event // event value for the filesystem action
Path() string // real path of the file or directory
Sys() interface{} // underlying data source (can return nil)
}
type isDirer interface {
isDir() (bool, error)
}
var _ fmt.Stringer = (*event)(nil)
var _ isDirer = (*event)(nil)
// String implements fmt.Stringer interface.
func (e *event) String() string {
return e.Event().String() + `: "` + e.Path() + `"`
}
var estr = map[Event]string{
Create: "notify.Create",
Remove: "notify.Remove",
Write: "notify.Write",
Rename: "notify.Rename",
// Display name for recursive event is added only for debugging
// purposes. It's an internal event after all and won't be exposed to the
// user. Having Recursive event printable is helpful, e.g. for reading
// testing failure messages:
//
// --- FAIL: TestWatchpoint (0.00 seconds)
// watchpoint_test.go:64: want diff=[notify.Remove notify.Create|notify.Remove];
// got [notify.Remove notify.Remove|notify.Create] (i=1)
//
// Yup, here the diff have Recursive event inside. Go figure.
recursive: "recursive",
omit: "omit",
}
+57
View File
@@ -0,0 +1,57 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build solaris
package notify
const (
osSpecificCreate Event = 0x00000100 << iota
osSpecificRemove
osSpecificWrite
osSpecificRename
// internal
// recursive is used to distinguish recursive eventsets from non-recursive ones
recursive
// omit is used for dispatching internal events; only those events are sent
// for which both the event and the watchpoint has omit in theirs event sets.
omit
)
const (
// FileAccess is an event reported when monitored file/directory was accessed.
FileAccess = fileAccess
// FileModified is an event reported when monitored file/directory was modified.
FileModified = fileModified
// FileAttrib is an event reported when monitored file/directory's ATTRIB
// was changed.
FileAttrib = fileAttrib
// FileDelete is an event reported when monitored file/directory was deleted.
FileDelete = fileDelete
// FileRenameTo to is an event reported when monitored file/directory was renamed.
FileRenameTo = fileRenameTo
// FileRenameFrom is an event reported when monitored file/directory was renamed.
FileRenameFrom = fileRenameFrom
// FileTrunc is an event reported when monitored file/directory was truncated.
FileTrunc = fileTrunc
// FileNoFollow is an flag to indicate not to follow symbolic links.
FileNoFollow = fileNoFollow
// Unmounted is an event reported when monitored filesystem was unmounted.
Unmounted = unmounted
// MountedOver is an event reported when monitored file/directory was mounted on.
MountedOver = mountedOver
)
var osestr = map[Event]string{
FileAccess: "notify.FileAccess",
FileModified: "notify.FileModified",
FileAttrib: "notify.FileAttrib",
FileDelete: "notify.FileDelete",
FileRenameTo: "notify.FileRenameTo",
FileRenameFrom: "notify.FileRenameFrom",
FileTrunc: "notify.FileTrunc",
FileNoFollow: "notify.FileNoFollow",
Unmounted: "notify.Unmounted",
MountedOver: "notify.MountedOver",
}
+71
View File
@@ -0,0 +1,71 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin,!kqueue
package notify
const (
osSpecificCreate = Event(FSEventsCreated)
osSpecificRemove = Event(FSEventsRemoved)
osSpecificWrite = Event(FSEventsModified)
osSpecificRename = Event(FSEventsRenamed)
// internal = Event(0x100000)
// recursive is used to distinguish recursive eventsets from non-recursive ones
recursive = Event(0x200000)
// omit is used for dispatching internal events; only those events are sent
// for which both the event and the watchpoint has omit in theirs event sets.
omit = Event(0x400000)
)
// FSEvents specific event values.
const (
FSEventsMustScanSubDirs Event = 0x00001
FSEventsUserDropped = 0x00002
FSEventsKernelDropped = 0x00004
FSEventsEventIdsWrapped = 0x00008
FSEventsHistoryDone = 0x00010
FSEventsRootChanged = 0x00020
FSEventsMount = 0x00040
FSEventsUnmount = 0x00080
FSEventsCreated = 0x00100
FSEventsRemoved = 0x00200
FSEventsInodeMetaMod = 0x00400
FSEventsRenamed = 0x00800
FSEventsModified = 0x01000
FSEventsFinderInfoMod = 0x02000
FSEventsChangeOwner = 0x04000
FSEventsXattrMod = 0x08000
FSEventsIsFile = 0x10000
FSEventsIsDir = 0x20000
FSEventsIsSymlink = 0x40000
)
var osestr = map[Event]string{
FSEventsMustScanSubDirs: "notify.FSEventsMustScanSubDirs",
FSEventsUserDropped: "notify.FSEventsUserDropped",
FSEventsKernelDropped: "notify.FSEventsKernelDropped",
FSEventsEventIdsWrapped: "notify.FSEventsEventIdsWrapped",
FSEventsHistoryDone: "notify.FSEventsHistoryDone",
FSEventsRootChanged: "notify.FSEventsRootChanged",
FSEventsMount: "notify.FSEventsMount",
FSEventsUnmount: "notify.FSEventsUnmount",
FSEventsInodeMetaMod: "notify.FSEventsInodeMetaMod",
FSEventsFinderInfoMod: "notify.FSEventsFinderInfoMod",
FSEventsChangeOwner: "notify.FSEventsChangeOwner",
FSEventsXattrMod: "notify.FSEventsXattrMod",
FSEventsIsFile: "notify.FSEventsIsFile",
FSEventsIsDir: "notify.FSEventsIsDir",
FSEventsIsSymlink: "notify.FSEventsIsSymlink",
}
type event struct {
fse FSEvent
event Event
}
func (ei *event) Event() Event { return ei.event }
func (ei *event) Path() string { return ei.fse.Path }
func (ei *event) Sys() interface{} { return &ei.fse }
func (ei *event) isDir() (bool, error) { return ei.fse.Flags&FSEventsIsDir != 0, nil }
+75
View File
@@ -0,0 +1,75 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build linux
package notify
import "golang.org/x/sys/unix"
// Platform independent event values.
const (
osSpecificCreate Event = 0x100000 << iota
osSpecificRemove
osSpecificWrite
osSpecificRename
// internal
// recursive is used to distinguish recursive eventsets from non-recursive ones
recursive
// omit is used for dispatching internal events; only those events are sent
// for which both the event and the watchpoint has omit in theirs event sets.
omit
)
// Inotify specific masks are legal, implemented events that are guaranteed to
// work with notify package on linux-based systems.
const (
InAccess = Event(unix.IN_ACCESS) // File was accessed
InModify = Event(unix.IN_MODIFY) // File was modified
InAttrib = Event(unix.IN_ATTRIB) // Metadata changed
InCloseWrite = Event(unix.IN_CLOSE_WRITE) // Writtable file was closed
InCloseNowrite = Event(unix.IN_CLOSE_NOWRITE) // Unwrittable file closed
InOpen = Event(unix.IN_OPEN) // File was opened
InMovedFrom = Event(unix.IN_MOVED_FROM) // File was moved from X
InMovedTo = Event(unix.IN_MOVED_TO) // File was moved to Y
InCreate = Event(unix.IN_CREATE) // Subfile was created
InDelete = Event(unix.IN_DELETE) // Subfile was deleted
InDeleteSelf = Event(unix.IN_DELETE_SELF) // Self was deleted
InMoveSelf = Event(unix.IN_MOVE_SELF) // Self was moved
)
var osestr = map[Event]string{
InAccess: "notify.InAccess",
InModify: "notify.InModify",
InAttrib: "notify.InAttrib",
InCloseWrite: "notify.InCloseWrite",
InCloseNowrite: "notify.InCloseNowrite",
InOpen: "notify.InOpen",
InMovedFrom: "notify.InMovedFrom",
InMovedTo: "notify.InMovedTo",
InCreate: "notify.InCreate",
InDelete: "notify.InDelete",
InDeleteSelf: "notify.InDeleteSelf",
InMoveSelf: "notify.InMoveSelf",
}
// Inotify behavior events are not **currently** supported by notify package.
const (
inDontFollow = Event(unix.IN_DONT_FOLLOW)
inExclUnlink = Event(unix.IN_EXCL_UNLINK)
inMaskAdd = Event(unix.IN_MASK_ADD)
inOneshot = Event(unix.IN_ONESHOT)
inOnlydir = Event(unix.IN_ONLYDIR)
)
type event struct {
sys unix.InotifyEvent
path string
event Event
}
func (e *event) Event() Event { return e.event }
func (e *event) Path() string { return e.path }
func (e *event) Sys() interface{} { return &e.sys }
func (e *event) isDir() (bool, error) { return e.sys.Mask&unix.IN_ISDIR != 0, nil }
+59
View File
@@ -0,0 +1,59 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin,kqueue dragonfly freebsd netbsd openbsd
package notify
import "syscall"
// TODO(pblaszczyk): ensure in runtime notify built-in event values do not
// overlap with platform-defined ones.
// Platform independent event values.
const (
osSpecificCreate Event = 0x0100 << iota
osSpecificRemove
osSpecificWrite
osSpecificRename
// internal
// recursive is used to distinguish recursive eventsets from non-recursive ones
recursive
// omit is used for dispatching internal events; only those events are sent
// for which both the event and the watchpoint has omit in theirs event sets.
omit
)
const (
// NoteDelete is an event reported when the unlink() system call was called
// on the file referenced by the descriptor.
NoteDelete = Event(syscall.NOTE_DELETE)
// NoteWrite is an event reported when a write occurred on the file
// referenced by the descriptor.
NoteWrite = Event(syscall.NOTE_WRITE)
// NoteExtend is an event reported when the file referenced by the
// descriptor was extended.
NoteExtend = Event(syscall.NOTE_EXTEND)
// NoteAttrib is an event reported when the file referenced
// by the descriptor had its attributes changed.
NoteAttrib = Event(syscall.NOTE_ATTRIB)
// NoteLink is an event reported when the link count on the file changed.
NoteLink = Event(syscall.NOTE_LINK)
// NoteRename is an event reported when the file referenced
// by the descriptor was renamed.
NoteRename = Event(syscall.NOTE_RENAME)
// NoteRevoke is an event reported when access to the file was revoked via
// revoke(2) or the underlying file system was unmounted.
NoteRevoke = Event(syscall.NOTE_REVOKE)
)
var osestr = map[Event]string{
NoteDelete: "notify.NoteDelete",
NoteWrite: "notify.NoteWrite",
NoteExtend: "notify.NoteExtend",
NoteAttrib: "notify.NoteAttrib",
NoteLink: "notify.NoteLink",
NoteRename: "notify.NoteRename",
NoteRevoke: "notify.NoteRevoke",
}
+118
View File
@@ -0,0 +1,118 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build windows
package notify
import (
"os"
"path/filepath"
"syscall"
)
// Platform independent event values.
const (
osSpecificCreate Event = 1 << (20 + iota)
osSpecificRemove
osSpecificWrite
osSpecificRename
// recursive is used to distinguish recursive eventsets from non-recursive ones
recursive
// omit is used for dispatching internal events; only those events are sent
// for which both the event and the watchpoint has omit in theirs event sets.
omit
// dirmarker TODO(pknap)
dirmarker
)
// ReadDirectoryChangesW filters
// On Windows the following events can be passed to Watch. A different set of
// events (see actions below) are received on the channel passed to Watch.
// For more information refer to
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx
const (
FileNotifyChangeFileName = Event(syscall.FILE_NOTIFY_CHANGE_FILE_NAME)
FileNotifyChangeDirName = Event(syscall.FILE_NOTIFY_CHANGE_DIR_NAME)
FileNotifyChangeAttributes = Event(syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES)
FileNotifyChangeSize = Event(syscall.FILE_NOTIFY_CHANGE_SIZE)
FileNotifyChangeLastWrite = Event(syscall.FILE_NOTIFY_CHANGE_LAST_WRITE)
FileNotifyChangeLastAccess = Event(syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS)
FileNotifyChangeCreation = Event(syscall.FILE_NOTIFY_CHANGE_CREATION)
FileNotifyChangeSecurity = Event(syscallFileNotifyChangeSecurity)
)
const (
fileNotifyChangeAll = 0x17f // logical sum of all FileNotifyChange* events.
fileNotifyChangeModified = fileNotifyChangeAll &^ (FileNotifyChangeFileName | FileNotifyChangeDirName)
)
// according to: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx
// this flag should be declared in: http://golang.org/src/pkg/syscall/ztypes_windows.go
const syscallFileNotifyChangeSecurity = 0x00000100
// ReadDirectoryChangesW actions
// The following events are returned on the channel passed to Watch, but cannot
// be passed to Watch itself (see filters above). You can find a table showing
// the relation between actions and filteres at
// https://github.com/rjeczalik/notify/issues/10#issuecomment-66179535
// The msdn documentation on actions is part of
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa364391(v=vs.85).aspx
const (
FileActionAdded = Event(syscall.FILE_ACTION_ADDED) << 12
FileActionRemoved = Event(syscall.FILE_ACTION_REMOVED) << 12
FileActionModified = Event(syscall.FILE_ACTION_MODIFIED) << 14
FileActionRenamedOldName = Event(syscall.FILE_ACTION_RENAMED_OLD_NAME) << 15
FileActionRenamedNewName = Event(syscall.FILE_ACTION_RENAMED_NEW_NAME) << 16
)
const fileActionAll = 0x7f000 // logical sum of all FileAction* events.
var osestr = map[Event]string{
FileNotifyChangeFileName: "notify.FileNotifyChangeFileName",
FileNotifyChangeDirName: "notify.FileNotifyChangeDirName",
FileNotifyChangeAttributes: "notify.FileNotifyChangeAttributes",
FileNotifyChangeSize: "notify.FileNotifyChangeSize",
FileNotifyChangeLastWrite: "notify.FileNotifyChangeLastWrite",
FileNotifyChangeLastAccess: "notify.FileNotifyChangeLastAccess",
FileNotifyChangeCreation: "notify.FileNotifyChangeCreation",
FileNotifyChangeSecurity: "notify.FileNotifyChangeSecurity",
FileActionAdded: "notify.FileActionAdded",
FileActionRemoved: "notify.FileActionRemoved",
FileActionModified: "notify.FileActionModified",
FileActionRenamedOldName: "notify.FileActionRenamedOldName",
FileActionRenamedNewName: "notify.FileActionRenamedNewName",
}
const (
fTypeUnknown uint8 = iota
fTypeFile
fTypeDirectory
)
// TODO(ppknap) : doc.
type event struct {
pathw []uint16
name string
ftype uint8
action uint32
filter uint32
e Event
}
func (e *event) Event() Event { return e.e }
func (e *event) Path() string { return filepath.Join(syscall.UTF16ToString(e.pathw), e.name) }
func (e *event) Sys() interface{} { return e.ftype }
func (e *event) isDir() (bool, error) {
if e.ftype != fTypeUnknown {
return e.ftype == fTypeDirectory, nil
}
fi, err := os.Stat(e.Path())
if err != nil {
return false, err
}
return fi.IsDir(), nil
}
+31
View File
@@ -0,0 +1,31 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build !darwin,!linux,!freebsd,!dragonfly,!netbsd,!openbsd,!windows
// +build !kqueue,!solaris
package notify
// Platform independent event values.
const (
osSpecificCreate Event = 1 << iota
osSpecificRemove
osSpecificWrite
osSpecificRename
// internal
// recursive is used to distinguish recursive eventsets from non-recursive ones
recursive
// omit is used for dispatching internal events; only those events are sent
// for which both the event and the watchpoint has omit in theirs event sets.
omit
)
var osestr = map[Event]string{}
type event struct{}
func (e *event) Event() (_ Event) { return }
func (e *event) Path() (_ string) { return }
func (e *event) Sys() (_ interface{}) { return }
func (e *event) isDir() (_ bool, _ error) { return }
+33
View File
@@ -0,0 +1,33 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
import (
"sort"
"strings"
"testing"
)
// S is a workaround for random event strings concatenation order.
func s(s string) string {
z := strings.Split(s, "|")
sort.StringSlice(z).Sort()
return strings.Join(z, "|")
}
// This test is not safe to run in parallel with others.
func TestEventString(t *testing.T) {
cases := map[Event]string{
Create: "notify.Create",
Create | Remove: "notify.Create|notify.Remove",
Create | Remove | Write: "notify.Create|notify.Remove|notify.Write",
Create | Write | Rename: "notify.Create|notify.Rename|notify.Write",
}
for e, str := range cases {
if s := s(e.String()); s != str {
t.Errorf("want s=%s; got %s (e=%#x)", str, s, e)
}
}
}
+22
View File
@@ -0,0 +1,22 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin,kqueue dragonfly freebsd netbsd openbsd solaris
package notify
type event struct {
p string
e Event
d bool
pe interface{}
}
func (e *event) Event() Event { return e.e }
func (e *event) Path() string { return e.p }
func (e *event) Sys() interface{} { return e.pe }
func (e *event) isDir() (bool, error) { return e.d, nil }
+128
View File
@@ -0,0 +1,128 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin,!kqueue
package notify_test
import (
"log"
"github.com/rjeczalik/notify"
)
// This example shows how to use FSEvents-specifc event values.
func ExampleWatch_darwin() {
// Make the channel buffered to ensure no event is dropped. Notify will drop
// an event if the receiver is not able to keep up the sending pace.
c := make(chan notify.EventInfo, 1)
// Set up a watchpoint listening for FSEvents-specific events within a
// current working directory. Dispatch each FSEventsChangeOwner and FSEventsMount
// events separately to c.
if err := notify.Watch(".", c, notify.FSEventsChangeOwner, notify.FSEventsMount); err != nil {
log.Fatal(err)
}
defer notify.Stop(c)
// Block until an event is received.
switch ei := <-c; ei.Event() {
case notify.FSEventsChangeOwner:
log.Println("The owner of", ei.Path(), "has changed.")
case notify.FSEventsMount:
log.Println("The path", ei.Path(), "has been mounted.")
}
}
// This example shows how to work with EventInfo's underlying FSEvent struct.
// Investigating notify.(*FSEvent).Flags field we are able to say whether
// the event's path is a file or a directory and many more.
func ExampleWatch_darwinDirFileSymlink() {
var must = func(err error) {
if err != nil {
log.Fatal(err)
}
}
var stop = func(c ...chan<- notify.EventInfo) {
for _, c := range c {
notify.Stop(c)
}
}
// Make the channels buffered to ensure no event is dropped. Notify will drop
// an event if the receiver is not able to keep up the sending pace.
dir := make(chan notify.EventInfo, 1)
file := make(chan notify.EventInfo, 1)
symlink := make(chan notify.EventInfo, 1)
all := make(chan notify.EventInfo, 1)
// Set up a single watchpoint listening for FSEvents-specific events on
// multiple user-provided channels.
must(notify.Watch(".", dir, notify.FSEventsIsDir))
must(notify.Watch(".", file, notify.FSEventsIsFile))
must(notify.Watch(".", symlink, notify.FSEventsIsSymlink))
must(notify.Watch(".", all, notify.All))
defer stop(dir, file, symlink, all)
// Block until an event is received.
select {
case ei := <-dir:
log.Println("The directory", ei.Path(), "has changed")
case ei := <-file:
log.Println("The file", ei.Path(), "has changed")
case ei := <-symlink:
log.Println("The symlink", ei.Path(), "has changed")
case ei := <-all:
var kind string
// Investigate underlying *notify.FSEvent struct to access more
// information about the event.
switch flags := ei.Sys().(*notify.FSEvent).Flags; {
case flags&notify.FSEventsIsFile != 0:
kind = "file"
case flags&notify.FSEventsIsDir != 0:
kind = "dir"
case flags&notify.FSEventsIsSymlink != 0:
kind = "symlink"
}
log.Printf("The %s under path %s has been %sd\n", kind, ei.Path(), ei.Event())
}
}
// FSEvents may report multiple filesystem actions with one, coalesced event.
// Notify unscoalesces such event and dispatches series of single events
// back to the user.
//
// This example shows how to coalesce events by investigating notify.(*FSEvent).ID
// field, for the science.
func ExampleWatch_darwinCoalesce() {
// Make the channels buffered to ensure no event is dropped. Notify will drop
// an event if the receiver is not able to keep up the sending pace.
c := make(chan notify.EventInfo, 4)
// Set up a watchpoint listetning for events within current working directory.
// Dispatch all platform-independent separately to c.
if err := notify.Watch(".", c, notify.All); err != nil {
log.Fatal(err)
}
defer notify.Stop(c)
var id uint64
var coalesced []notify.EventInfo
for ei := range c {
switch n := ei.Sys().(*notify.FSEvent).ID; {
case id == 0:
id = n
coalesced = []notify.EventInfo{ei}
case id == n:
coalesced = append(coalesced, ei)
default:
log.Printf("FSEvents reported a filesystem action with the following"+
" coalesced events %v groupped by %d ID\n", coalesced, id)
return
}
}
}
+84
View File
@@ -0,0 +1,84 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build linux
package notify_test
import (
"log"
"golang.org/x/sys/unix"
"github.com/rjeczalik/notify"
)
// This example shows how to watch changes made on file-system by text editor
// when saving a file. Usually, either InCloseWrite or InMovedTo (when swapping
// with a temporary file) event is created.
func ExampleWatch_linux() {
// Make the channel buffered to ensure no event is dropped. Notify will drop
// an event if the receiver is not able to keep up the sending pace.
c := make(chan notify.EventInfo, 1)
// Set up a watchpoint listening for inotify-specific events within a
// current working directory. Dispatch each InCloseWrite and InMovedTo
// events separately to c.
if err := notify.Watch(".", c, notify.InCloseWrite, notify.InMovedTo); err != nil {
log.Fatal(err)
}
defer notify.Stop(c)
// Block until an event is received.
switch ei := <-c; ei.Event() {
case notify.InCloseWrite:
log.Println("Editing of", ei.Path(), "file is done.")
case notify.InMovedTo:
log.Println("File", ei.Path(), "was swapped/moved into the watched directory.")
}
}
// This example shows how to use Sys() method from EventInfo interface to tie
// two separate events generated by rename(2) function.
func ExampleWatch_linuxMove() {
// Make the channel buffered to ensure no event is dropped. Notify will drop
// an event if the receiver is not able to keep up the sending pace.
c := make(chan notify.EventInfo, 2)
// Set up a watchpoint listening for inotify-specific events within a
// current working directory. Dispatch each InMovedFrom and InMovedTo
// events separately to c.
if err := notify.Watch(".", c, notify.InMovedFrom, notify.InMovedTo); err != nil {
log.Fatal(err)
}
defer notify.Stop(c)
// Inotify reports move filesystem action by sending two events tied with
// unique cookie value (uint32): one of the events is of InMovedFrom type
// carrying move source path, while the second one is of InMoveTo type
// carrying move destination path.
moves := make(map[uint32]struct {
From string
To string
})
// Wait for moves.
for ei := range c {
cookie := ei.Sys().(*unix.InotifyEvent).Cookie
info := moves[cookie]
switch ei.Event() {
case notify.InMovedFrom:
info.From = ei.Path()
case notify.InMovedTo:
info.To = ei.Path()
}
moves[cookie] = info
if cookie != 0 && info.From != "" && info.To != "" {
log.Println("File:", info.From, "was renamed to", info.To)
delete(moves, cookie)
}
}
}
+40
View File
@@ -0,0 +1,40 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build windows
package notify_test
import (
"log"
"github.com/rjeczalik/notify"
)
// This example shows how to watch directory-name changes in the working directory subtree.
func ExampleWatch_windows() {
// Make the channel buffered to ensure no event is dropped. Notify will drop
// an event if the receiver is not able to keep up the sending pace.
c := make(chan notify.EventInfo, 4)
// Since notify package behaves exactly like ReadDirectoryChangesW function,
// we must register notify.FileNotifyChangeDirName filter and wait for one
// of FileAction* events.
if err := notify.Watch("./...", c, notify.FileNotifyChangeDirName); err != nil {
log.Fatal(err)
}
defer notify.Stop(c)
// Wait for actions.
for ei := range c {
switch ei.Event() {
case notify.FileActionAdded, notify.FileActionRenamedNewName:
log.Println("Created:", ei.Path())
case notify.FileActionRemoved, notify.FileActionRenamedOldName:
log.Println("Removed:", ei.Path())
case notify.FileActionModified:
panic("notify: unexpected action")
}
}
}
+87
View File
@@ -0,0 +1,87 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify_test
import (
"log"
"path/filepath"
"time"
"github.com/rjeczalik/notify"
)
// This is a basic example showing how to work with notify.Watch function.
func ExampleWatch() {
// Make the channel buffered to ensure no event is dropped. Notify will drop
// an event if the receiver is not able to keep up the sending pace.
c := make(chan notify.EventInfo, 1)
// Set up a watchpoint listening on events within current working directory.
// Dispatch each create and remove events separately to c.
if err := notify.Watch(".", c, notify.Create, notify.Remove); err != nil {
log.Fatal(err)
}
defer notify.Stop(c)
// Block until an event is received.
ei := <-c
log.Println("Got event:", ei)
}
// This example shows how to set up a recursive watchpoint.
func ExampleWatch_recursive() {
// Make the channel buffered to ensure no event is dropped. Notify will drop
// an event if the receiver is not able to keep up the sending pace.
c := make(chan notify.EventInfo, 1)
// Set up a watchpoint listening for events within a directory tree rooted
// at current working directory. Dispatch remove events to c.
if err := notify.Watch("./...", c, notify.Remove); err != nil {
log.Fatal(err)
}
defer notify.Stop(c)
// Block until an event is received.
ei := <-c
log.Println("Got event:", ei)
}
// This example shows why it is important to not create leaks by stoping
// a channel when it's no longer being used.
func ExampleStop() {
waitfor := func(path string, e notify.Event, timeout time.Duration) bool {
dir, file := filepath.Split(path)
c := make(chan notify.EventInfo, 1)
if err := notify.Watch(dir, c, e); err != nil {
log.Fatal(err)
}
// Clean up watchpoint associated with c. If Stop was not called upon
// return the channel would be leaked as notify holds the only reference
// to it and does not release it on its own.
defer notify.Stop(c)
t := time.After(timeout)
for {
select {
case ei := <-c:
if filepath.Base(ei.Path()) == file {
return true
}
case <-t:
return false
}
}
}
if waitfor("index.lock", notify.Create, 5*time.Second) {
log.Println("The git repository was locked")
}
if waitfor("index.lock", notify.Remove, 5*time.Second) {
log.Println("The git repository was unlocked")
}
}
+275
View File
@@ -0,0 +1,275 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
import (
"errors"
"io/ioutil"
"os"
"path/filepath"
)
var errSkip = errors.New("notify: skip")
type walkPathFunc func(nd node, isbase bool) error
type walkFunc func(node) error
func errnotexist(name string) error {
return &os.PathError{
Op: "Node",
Path: name,
Err: os.ErrNotExist,
}
}
type node struct {
Name string
Watch watchpoint
Child map[string]node
}
func newnode(name string) node {
return node{
Name: name,
Watch: make(watchpoint),
Child: make(map[string]node),
}
}
func (nd node) addchild(name, base string) node {
child, ok := nd.Child[base]
if !ok {
child = newnode(name)
nd.Child[base] = child
}
return child
}
func (nd node) Add(name string) node {
i := indexbase(nd.Name, name)
if i == -1 {
return node{}
}
for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
nd = nd.addchild(name[:i+j], name[i:i+j])
i += j + 1
}
return nd.addchild(name, name[i:])
}
func (nd node) AddDir(fn walkFunc) error {
stack := []node{nd}
Traverse:
for n := len(stack); n != 0; n = len(stack) {
nd, stack = stack[n-1], stack[:n-1]
switch err := fn(nd); err {
case nil:
case errSkip:
continue Traverse
default:
return &os.PathError{
Op: "error while traversing",
Path: nd.Name,
Err: err,
}
}
// TODO(rjeczalik): tolerate open failures - add failed names to
// AddDirError and notify users which names are not added to the tree.
fi, err := ioutil.ReadDir(nd.Name)
if err != nil {
return err
}
for _, fi := range fi {
if fi.Mode()&(os.ModeSymlink|os.ModeDir) == os.ModeDir {
name := filepath.Join(nd.Name, fi.Name())
stack = append(stack, nd.addchild(name, name[len(nd.Name)+1:]))
}
}
}
return nil
}
func (nd node) Get(name string) (node, error) {
i := indexbase(nd.Name, name)
if i == -1 {
return node{}, errnotexist(name)
}
ok := false
for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
if nd, ok = nd.Child[name[i:i+j]]; !ok {
return node{}, errnotexist(name)
}
i += j + 1
}
if nd, ok = nd.Child[name[i:]]; !ok {
return node{}, errnotexist(name)
}
return nd, nil
}
func (nd node) Del(name string) error {
i := indexbase(nd.Name, name)
if i == -1 {
return errnotexist(name)
}
stack := []node{nd}
ok := false
for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
if nd, ok = nd.Child[name[i:i+j]]; !ok {
return errnotexist(name[:i+j])
}
stack = append(stack, nd)
}
if nd, ok = nd.Child[name[i:]]; !ok {
return errnotexist(name)
}
nd.Child = nil
nd.Watch = nil
for name, i = base(nd.Name), len(stack); i != 0; name, i = base(nd.Name), i-1 {
nd = stack[i-1]
if nd := nd.Child[name]; len(nd.Watch) > 1 || len(nd.Child) != 0 {
break
} else {
nd.Child = nil
nd.Watch = nil
}
delete(nd.Child, name)
}
return nil
}
func (nd node) Walk(fn walkFunc) error {
stack := []node{nd}
Traverse:
for n := len(stack); n != 0; n = len(stack) {
nd, stack = stack[n-1], stack[:n-1]
switch err := fn(nd); err {
case nil:
case errSkip:
continue Traverse
default:
return err
}
for name, nd := range nd.Child {
if name == "" {
// Node storing inactive watchpoints has empty name, skip it
// form traversing. Root node has also an empty name, but it
// never has a parent node.
continue
}
stack = append(stack, nd)
}
}
return nil
}
func (nd node) WalkPath(name string, fn walkPathFunc) error {
i := indexbase(nd.Name, name)
if i == -1 {
return errnotexist(name)
}
ok := false
for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
switch err := fn(nd, false); err {
case nil:
case errSkip:
return nil
default:
return err
}
if nd, ok = nd.Child[name[i:i+j]]; !ok {
return errnotexist(name[:i+j])
}
i += j + 1
}
switch err := fn(nd, false); err {
case nil:
case errSkip:
return nil
default:
return err
}
if nd, ok = nd.Child[name[i:]]; !ok {
return errnotexist(name)
}
switch err := fn(nd, true); err {
case nil, errSkip:
return nil
default:
return err
}
}
type root struct {
nd node
}
func (r root) addroot(name string) node {
if vol := filepath.VolumeName(name); vol != "" {
root, ok := r.nd.Child[vol]
if !ok {
root = r.nd.addchild(vol, vol)
}
return root
}
return r.nd
}
func (r root) root(name string) (node, error) {
if vol := filepath.VolumeName(name); vol != "" {
nd, ok := r.nd.Child[vol]
if !ok {
return node{}, errnotexist(name)
}
return nd, nil
}
return r.nd, nil
}
func (r root) Add(name string) node {
return r.addroot(name).Add(name)
}
func (r root) AddDir(dir string, fn walkFunc) error {
return r.Add(dir).AddDir(fn)
}
func (r root) Del(name string) error {
nd, err := r.root(name)
if err != nil {
return err
}
return nd.Del(name)
}
func (r root) Get(name string) (node, error) {
nd, err := r.root(name)
if err != nil {
return node{}, err
}
if nd.Name != name {
if nd, err = nd.Get(name); err != nil {
return node{}, err
}
}
return nd, nil
}
func (r root) Walk(name string, fn walkFunc) error {
nd, err := r.Get(name)
if err != nil {
return err
}
return nd.Walk(fn)
}
func (r root) WalkPath(name string, fn walkPathFunc) error {
nd, err := r.root(name)
if err != nil {
return err
}
return nd.WalkPath(name, fn)
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// BUG(rjeczalik): Notify does not collect watchpoints, when underlying watches
// were removed by their os-specific watcher implementations. Instead users are
// advised to listen on persistent paths to have guarantee they receive events
// for the whole lifetime of their applications (to discuss see #69).
// BUG(ppknap): Linux (inotify) does not support watcher behavior masks like
// InOneshot, InOnlydir etc. Instead users are advised to perform the filtering
// themselves (to discuss see #71).
// BUG(ppknap): Notify was not tested for short path name support under Windows
// (ReadDirectoryChangesW).
// BUG(ppknap): Windows (ReadDirectoryChangesW) cannot recognize which notification
// triggers FileActionModified event. (to discuss see #75).
package notify
var defaultTree = newTree()
// Watch sets up a watchpoint on path listening for events given by the events
// argument.
//
// File or directory given by the path must exist, otherwise Watch will fail
// with non-nil error. Notify resolves, for its internal purpose, any symlinks
// the provided path may contain, so it may fail if the symlinks form a cycle.
// It does so, since not all watcher implementations treat passed paths as-is.
// E.g. FSEvents reports a real path for every event, setting a watchpoint
// on /tmp will report events with paths rooted at /private/tmp etc.
//
// The c almost always is a buffered channel. Watch will not block sending to c
// - the caller must ensure that c has sufficient buffer space to keep up with
// the expected event rate.
//
// It is allowed to pass the same channel multiple times with different event
// list or different paths. Calling Watch with different event lists for a single
// watchpoint expands its event set. The only way to shrink it, is to call
// Stop on its channel.
//
// Calling Watch with empty event list does expand nor shrink watchpoint's event
// set. If c is the first channel to listen for events on the given path, Watch
// will seamlessly create a watch on the filesystem.
//
// Notify dispatches copies of single filesystem event to all channels registered
// for each path. If a single filesystem event contains multiple coalesced events,
// each of them is dispatched separately. E.g. the following filesystem change:
//
// ~ $ echo Hello > Notify.txt
//
// dispatches two events - notify.Create and notify.Write. However, it may depend
// on the underlying watcher implementation whether OS reports both of them.
//
// Windows and recursive watches
//
// If a directory which path was used to create recursive watch under Windows
// gets deleted, the OS will not report such event. It is advised to keep in
// mind this limitation while setting recursive watchpoints for your application,
// e.g. use persistent paths like %userprofile% or watch additionally parent
// directory of a recursive watchpoint in order to receive delete events for it.
func Watch(path string, c chan<- EventInfo, events ...Event) error {
return defaultTree.Watch(path, c, events...)
}
// Stop removes all watchpoints registered for c. All underlying watches are
// also removed, for which c was the last channel listening for events.
//
// Stop does not close c. When Stop returns, it is guaranteed that c will
// receive no more signals.
func Stop(c chan<- EventInfo) {
defaultTree.Stop(c)
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build linux
package notify
import "testing"
func TestNotifySystemAndGlobalMix(t *testing.T) {
n := NewNotifyTest(t, "testdata/vfs.txt")
defer n.Close()
ch := NewChans(2)
n.Watch("src/github.com/rjeczalik/fs", ch[0], Create)
n.Watch("src/github.com/rjeczalik/fs", ch[1], InCreate)
cases := []NCase{
{
Event: icreate(n.W(), "src/github.com/rjeczalik/fs/.main.cc.swr"),
Receiver: Chans{ch[0], ch[1]},
},
}
n.ExpectNotifyEvents(cases, ch)
}
func TestUnknownEvent(t *testing.T) {
n := NewNotifyTest(t, "testdata/vfs.txt")
defer n.Close()
ch := NewChans(1)
n.WatchErr("src/github.com/rjeczalik/fs", ch[0], nil, inExclUnlink)
}
+60
View File
@@ -0,0 +1,60 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build windows
package notify
import "testing"
func TestNotifySystemSpecificEvent(t *testing.T) {
n := NewNotifyTest(t, "testdata/vfs.txt")
defer n.Close()
ch := NewChans(1)
n.Watch("src/github.com/rjeczalik/fs", ch[0], FileNotifyChangeFileName, FileNotifyChangeSize)
cases := []NCase{
{
Event: rremove(n.W(), "src/github.com/rjeczalik/fs/fs.go"),
Receiver: Chans{ch[0]},
},
{
Event: rwrite(n.W(), "src/github.com/rjeczalik/fs/README.md", []byte("XD")),
Receiver: Chans{ch[0]},
},
}
n.ExpectNotifyEvents(cases, ch)
}
func TestUnknownEvent(t *testing.T) {
n := NewNotifyTest(t, "testdata/vfs.txt")
defer n.Close()
ch := NewChans(1)
n.WatchErr("src/github.com/rjeczalik/fs", ch[0], nil, FileActionAdded)
}
func TestNotifySystemAndGlobalMix(t *testing.T) {
n := NewNotifyTest(t, "testdata/vfs.txt")
defer n.Close()
ch := NewChans(3)
n.Watch("src/github.com/rjeczalik/fs", ch[0], Create)
n.Watch("src/github.com/rjeczalik/fs", ch[1], FileNotifyChangeFileName)
n.Watch("src/github.com/rjeczalik/fs", ch[2], FileNotifyChangeDirName)
cases := []NCase{
{
Event: rcreate(n.W(), "src/github.com/rjeczalik/fs/.main.cc.swr"),
Receiver: Chans{ch[0], ch[1]},
},
}
n.ExpectNotifyEvents(cases, ch)
}
+103
View File
@@ -0,0 +1,103 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin linux freebsd dragonfly netbsd openbsd windows solaris
package notify
import "testing"
func TestNotifyExample(t *testing.T) {
n := NewNotifyTest(t, "testdata/vfs.txt")
defer n.Close()
ch := NewChans(3)
// Watch-points can be set explicitly via Watch/Stop calls...
n.Watch("src/github.com/rjeczalik/fs", ch[0], Write)
n.Watch("src/github.com/pblaszczyk/qttu", ch[0], Write)
n.Watch("src/github.com/pblaszczyk/qttu/...", ch[1], Create)
n.Watch("src/github.com/rjeczalik/fs/cmd/...", ch[2], Remove)
cases := []NCase{
// i=0
{
Event: write(n.W(), "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
Receiver: Chans{ch[0]},
},
// TODO(rjeczalik): #62
// i=1
// {
// Event: write(n.W(), "src/github.com/pblaszczyk/qttu/README.md", []byte("XD")),
// Receiver: Chans{ch[0]},
// },
// i=2
{
Event: write(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/go.go", []byte("XD")),
Receiver: nil,
},
// i=3
{
Event: create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swp"),
Receiver: Chans{ch[1]},
},
// i=4
{
Event: create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swo"),
Receiver: Chans{ch[1]},
},
// i=5
{
Event: remove(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/go.go"),
Receiver: Chans{ch[2]},
},
}
n.ExpectNotifyEvents(cases, ch)
// ...or using Call structures.
stops := [...]Call{
// i=0
{
F: FuncStop,
C: ch[0],
},
// i=1
{
F: FuncStop,
C: ch[1],
},
}
n.Call(stops[:]...)
cases = []NCase{
// i=0
{
Event: write(n.W(), "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
Receiver: nil,
},
// i=1
{
Event: write(n.W(), "src/github.com/pblaszczyk/qttu/README.md", []byte("XD")),
Receiver: nil,
},
// i=2
{
Event: create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swr"),
Receiver: nil,
},
// i=3
{
Event: remove(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/main.go"),
Receiver: Chans{ch[2]},
},
}
n.ExpectNotifyEvents(cases, ch)
}
func TestStop(t *testing.T) {
t.Skip("TODO(rjeczalik)")
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build windows
package notify
import (
"syscall"
"time"
"unsafe"
)
var modkernel32 = syscall.NewLazyDLL("kernel32.dll")
var procSetSystemFileCacheSize = modkernel32.NewProc("SetSystemFileCacheSize")
var zero = uintptr(1<<(unsafe.Sizeof(uintptr(0))*8) - 1)
func Sync() {
// TODO(pknap): does not work without admin privileges, but I'm going
// to hack it.
// r, _, err := procSetSystemFileCacheSize.Call(none, none, 0)
// if r == 0 {
// dbgprint("SetSystemFileCacheSize error:", err)
// }
}
// UpdateWait pauses the program for some minimal amount of time. This function
// is required only by implementations which work asynchronously. It gives
// watcher structure time to update its internal state.
func UpdateWait() {
time.Sleep(50 * time.Millisecond)
}
+18
View File
@@ -0,0 +1,18 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build !windows
package notify
import "golang.org/x/sys/unix"
func Sync() {
unix.Sync()
}
// UpdateWait is required only by windows watcher implementation. On other
// platforms this function is no-op.
func UpdateWait() {
}
+56
View File
@@ -0,0 +1,56 @@
src/github.com/pblaszczyk/qttu/.travis.yml
src/github.com/pblaszczyk/qttu/include/qttu/detail/registrator.hh
src/github.com/pblaszczyk/qttu/include/qttu/detail/registry.hh
src/github.com/pblaszczyk/qttu/include/qttu/runner.hh
src/github.com/pblaszczyk/qttu/LICENSE
src/github.com/pblaszczyk/qttu/qttu.pri
src/github.com/pblaszczyk/qttu/qttu.pro
src/github.com/pblaszczyk/qttu/README.md
src/github.com/pblaszczyk/qttu/src/main.cc
src/github.com/pblaszczyk/qttu/src/reg.cc
src/github.com/ppknap/link/.travis.yml
src/github.com/ppknap/link/include/coost/link/definitions.hpp
src/github.com/ppknap/link/include/coost/link/detail/bundle.hpp
src/github.com/ppknap/link/include/coost/link/detail/container_invoker.hpp
src/github.com/ppknap/link/include/coost/link/detail/container_value_trait.hpp
src/github.com/ppknap/link/include/coost/link/detail/dummy_type.hpp
src/github.com/ppknap/link/include/coost/link/detail/function_trait.hpp
src/github.com/ppknap/link/include/coost/link/detail/immediate_invoker.hpp
src/github.com/ppknap/link/include/coost/link/detail/stdhelpers/always_same.hpp
src/github.com/ppknap/link/include/coost/link/detail/stdhelpers/make_unique.hpp
src/github.com/ppknap/link/include/coost/link/detail/vertex.hpp
src/github.com/ppknap/link/include/coost/link/detail/wire.hpp
src/github.com/ppknap/link/include/coost/link/link.hpp
src/github.com/ppknap/link/include/coost/link.hpp
src/github.com/ppknap/link/Jamroot.jam
src/github.com/ppknap/link/LICENSE.md
src/github.com/ppknap/link/README.md
src/github.com/ppknap/link/test/counter_helper.hpp
src/github.com/ppknap/link/test/Jamfile.jam
src/github.com/ppknap/link/test/test_circular_calls.cpp
src/github.com/ppknap/link/test/test_container.cpp
src/github.com/ppknap/link/test/test_copy.cpp
src/github.com/ppknap/link/test/test_destructor.cpp
src/github.com/ppknap/link/test/test_immediate.cpp
src/github.com/ppknap/link/test/test_initialize.cpp
src/github.com/rjeczalik/fs/.travis.yml
src/github.com/rjeczalik/fs/appveyor.yml
src/github.com/rjeczalik/fs/cmd/gotree/go.go
src/github.com/rjeczalik/fs/cmd/gotree/main.go
src/github.com/rjeczalik/fs/cmd/mktree/main.go
src/github.com/rjeczalik/fs/fs.go
src/github.com/rjeczalik/fs/fsutil/fixture_test.go
src/github.com/rjeczalik/fs/fsutil/fsutil.go
src/github.com/rjeczalik/fs/fsutil/fsutil_test.go
src/github.com/rjeczalik/fs/fsutil/rel.go
src/github.com/rjeczalik/fs/fsutil/rel_test.go
src/github.com/rjeczalik/fs/fsutil/tee.go
src/github.com/rjeczalik/fs/fsutil/tee_test.go
src/github.com/rjeczalik/fs/LICENSE
src/github.com/rjeczalik/fs/memfs/memfs.go
src/github.com/rjeczalik/fs/memfs/memfs_test.go
src/github.com/rjeczalik/fs/memfs/tree.go
src/github.com/rjeczalik/fs/memfs/tree_test.go
src/github.com/rjeczalik/fs/memfs/util.go
src/github.com/rjeczalik/fs/memfs/util_test.go
src/github.com/rjeczalik/fs/README.md
+955
View File
@@ -0,0 +1,955 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
"testing"
"time"
)
// NOTE(rjeczalik): some useful environment variables:
//
// - NOTIFY_DEBUG gives some extra information about generated events
// - NOTIFY_TIMEOUT allows for changing default wait time for watcher's
// events
// - NOTIFY_TMP allows for changing location of temporary directory trees
// created for test purpose
var wd string
func init() {
var err error
if wd, err = os.Getwd(); err != nil {
panic("Getwd()=" + err.Error())
}
}
func timeout() time.Duration {
if s := os.Getenv("NOTIFY_TIMEOUT"); s != "" {
if t, err := time.ParseDuration(s); err == nil {
return t
}
}
return 2 * time.Second
}
func vfs() (string, string) {
if s := os.Getenv("NOTIFY_TMP"); s != "" {
return filepath.Split(s)
}
return "testdata", ""
}
func isDir(path string) bool {
r := path[len(path)-1]
return r == '\\' || r == '/'
}
func tmpcreateall(tmp string, path string) error {
isdir := isDir(path)
path = filepath.Join(tmp, filepath.FromSlash(path))
if isdir {
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
} else {
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return err
}
f, err := os.Create(path)
if err != nil {
return err
}
if err := nonil(f.Sync(), f.Close()); err != nil {
return err
}
}
return nil
}
func tmpcreate(root, path string) (bool, error) {
isdir := isDir(path)
path = filepath.Join(root, filepath.FromSlash(path))
if isdir {
if err := os.Mkdir(path, 0755); err != nil {
return false, err
}
} else {
f, err := os.Create(path)
if err != nil {
return false, err
}
if err := nonil(f.Sync(), f.Close()); err != nil {
return false, err
}
}
return isdir, nil
}
func tmptree(root, list string) (string, error) {
f, err := os.Open(list)
if err != nil {
return "", err
}
defer f.Close()
if root == "" {
if root, err = ioutil.TempDir(vfs()); err != nil {
return "", err
}
}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
if err := tmpcreateall(root, scanner.Text()); err != nil {
return "", err
}
}
if err := scanner.Err(); err != nil {
return "", err
}
return root, nil
}
func callern(n int) string {
_, file, line, ok := runtime.Caller(n)
if !ok {
return "<unknown>"
}
return filepath.Base(file) + ":" + strconv.Itoa(line)
}
func caller() string {
return callern(3)
}
type WCase struct {
Action func()
Events []EventInfo
}
func (cas WCase) String() string {
s := make([]string, 0, len(cas.Events))
for _, ei := range cas.Events {
s = append(s, "Event("+ei.Event().String()+")@"+filepath.FromSlash(ei.Path()))
}
return strings.Join(s, ", ")
}
type W struct {
Watcher watcher
C chan EventInfo
Timeout time.Duration
t *testing.T
root string
}
func newWatcherTest(t *testing.T, tree string) *W {
root, err := tmptree("", filepath.FromSlash(tree))
if err != nil {
t.Fatalf(`tmptree("", %q)=%v`, tree, err)
}
root, _, err = cleanpath(root)
if err != nil {
t.Fatalf(`cleanpath(%q)=%v`, root, err)
}
Sync()
return &W{
t: t,
root: root,
}
}
func NewWatcherTest(t *testing.T, tree string, events ...Event) *W {
w := newWatcherTest(t, tree)
if len(events) == 0 {
events = []Event{Create, Remove, Write, Rename}
}
if rw, ok := w.watcher().(recursiveWatcher); ok {
if err := rw.RecursiveWatch(w.root, joinevents(events)); err != nil {
t.Fatalf("RecursiveWatch(%q, All)=%v", w.root, err)
}
} else {
fn := func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if fi.IsDir() {
if err := w.watcher().Watch(path, joinevents(events)); err != nil {
return err
}
}
return nil
}
if err := filepath.Walk(w.root, fn); err != nil {
t.Fatalf("Walk(%q, fn)=%v", w.root, err)
}
}
drainall(w.C)
return w
}
func (w *W) clean(path string) string {
path, isrec, err := cleanpath(filepath.Join(w.root, path))
if err != nil {
w.Fatalf("cleanpath(%q)=%v", path, err)
}
if isrec {
path = path + "..."
}
return path
}
func (w *W) Fatal(v interface{}) {
w.t.Fatalf("%s: %v", caller(), v)
}
func (w *W) Fatalf(format string, v ...interface{}) {
w.t.Fatalf("%s: %s", caller(), fmt.Sprintf(format, v...))
}
func (w *W) Watch(path string, e Event) {
if err := w.watcher().Watch(w.clean(path), e); err != nil {
w.Fatalf("Watch(%s, %v)=%v", path, e, err)
}
}
func (w *W) Rewatch(path string, olde, newe Event) {
if err := w.watcher().Rewatch(w.clean(path), olde, newe); err != nil {
w.Fatalf("Rewatch(%s, %v, %v)=%v", path, olde, newe, err)
}
}
func (w *W) Unwatch(path string) {
if err := w.watcher().Unwatch(w.clean(path)); err != nil {
w.Fatalf("Unwatch(%s)=%v", path, err)
}
}
func (w *W) RecursiveWatch(path string, e Event) {
rw, ok := w.watcher().(recursiveWatcher)
if !ok {
w.Fatal("watcher does not implement recursive watching on this platform")
}
if err := rw.RecursiveWatch(w.clean(path), e); err != nil {
w.Fatalf("RecursiveWatch(%s, %v)=%v", path, e, err)
}
}
func (w *W) RecursiveRewatch(oldp, newp string, olde, newe Event) {
rw, ok := w.watcher().(recursiveWatcher)
if !ok {
w.Fatal("watcher does not implement recursive watching on this platform")
}
if err := rw.RecursiveRewatch(w.clean(oldp), w.clean(newp), olde, newe); err != nil {
w.Fatalf("RecursiveRewatch(%s, %s, %v, %v)=%v", oldp, newp, olde, newe, err)
}
}
func (w *W) RecursiveUnwatch(path string) {
rw, ok := w.watcher().(recursiveWatcher)
if !ok {
w.Fatal("watcher does not implement recursive watching on this platform")
}
if err := rw.RecursiveUnwatch(w.clean(path)); err != nil {
w.Fatalf("RecursiveUnwatch(%s)=%v", path, err)
}
}
func (w *W) initwatcher(buffer int) {
c := make(chan EventInfo, buffer)
w.Watcher = newWatcher(c)
w.C = c
}
func (w *W) watcher() watcher {
if w.Watcher == nil {
w.initwatcher(512)
}
return w.Watcher
}
func (w *W) c() chan EventInfo {
if w.C == nil {
w.initwatcher(512)
}
return w.C
}
func (w *W) timeout() time.Duration {
if w.Timeout != 0 {
return w.Timeout
}
return timeout()
}
func (w *W) Close() error {
defer os.RemoveAll(w.root)
if err := w.watcher().Close(); err != nil {
w.Fatalf("w.Watcher.Close()=%v", err)
}
return nil
}
func EqualEventInfo(want, got EventInfo) error {
if got.Event() != want.Event() {
return fmt.Errorf("want Event()=%v; got %v (path=%s)", want.Event(),
got.Event(), want.Path())
}
path := strings.TrimRight(filepath.FromSlash(want.Path()), `/\`)
if !strings.HasSuffix(got.Path(), path) {
return fmt.Errorf("want Path()=%s; got %s (event=%v)", path, got.Path(),
want.Event())
}
return nil
}
func HasEventInfo(want, got Event, p string) error {
if got&want != want {
return fmt.Errorf("want Event=%v; got %v (path=%s)", want,
got, p)
}
return nil
}
func EqualCall(want, got Call) error {
if want.F != got.F {
return fmt.Errorf("want F=%v; got %v (want.P=%q, got.P=%q)", want.F, got.F, want.P, got.P)
}
if got.E != want.E {
return fmt.Errorf("want E=%v; got %v (want.P=%q, got.P=%q)", want.E, got.E, want.P, got.P)
}
if got.NE != want.NE {
return fmt.Errorf("want NE=%v; got %v (want.P=%q, got.P=%q)", want.NE, got.NE, want.P, got.P)
}
if want.C != got.C {
return fmt.Errorf("want C=%p; got %p (want.P=%q, got.P=%q)", want.C, got.C, want.P, got.P)
}
if want := filepath.FromSlash(want.P); !strings.HasSuffix(got.P, want) {
return fmt.Errorf("want P=%s; got %s", want, got.P)
}
if want := filepath.FromSlash(want.NP); !strings.HasSuffix(got.NP, want) {
return fmt.Errorf("want NP=%s; got %s", want, got.NP)
}
return nil
}
func create(w *W, path string) WCase {
return WCase{
Action: func() {
isdir, err := tmpcreate(w.root, filepath.FromSlash(path))
if err != nil {
w.Fatalf("tmpcreate(%q, %q)=%v", w.root, path, err)
}
if isdir {
dbgprintf("[FS] os.Mkdir(%q)\n", path)
} else {
dbgprintf("[FS] os.Create(%q)\n", path)
}
},
Events: []EventInfo{
&Call{P: path, E: Create},
},
}
}
func remove(w *W, path string) WCase {
return WCase{
Action: func() {
if err := os.RemoveAll(filepath.Join(w.root, filepath.FromSlash(path))); err != nil {
w.Fatal(err)
}
dbgprintf("[FS] os.Remove(%q)\n", path)
},
Events: []EventInfo{
&Call{P: path, E: Remove},
},
}
}
func rename(w *W, oldpath, newpath string) WCase {
return WCase{
Action: func() {
err := os.Rename(filepath.Join(w.root, filepath.FromSlash(oldpath)),
filepath.Join(w.root, filepath.FromSlash(newpath)))
if err != nil {
w.Fatal(err)
}
dbgprintf("[FS] os.Rename(%q, %q)\n", oldpath, newpath)
},
Events: []EventInfo{
&Call{P: newpath, E: Rename},
},
}
}
func write(w *W, path string, p []byte) WCase {
return WCase{
Action: func() {
f, err := os.OpenFile(filepath.Join(w.root, filepath.FromSlash(path)),
os.O_WRONLY, 0644)
if err != nil {
w.Fatalf("OpenFile(%q)=%v", path, err)
}
if _, err := f.Write(p); err != nil {
w.Fatalf("Write(%q)=%v", path, err)
}
if err := nonil(f.Sync(), f.Close()); err != nil {
w.Fatalf("Sync(%q)/Close(%q)=%v", path, path, err)
}
dbgprintf("[FS] Write(%q)\n", path)
},
Events: []EventInfo{
&Call{P: path, E: Write},
},
}
}
func drainall(c chan EventInfo) (ei []EventInfo) {
time.Sleep(50 * time.Millisecond)
for {
select {
case e := <-c:
ei = append(ei, e)
runtime.Gosched()
default:
return
}
}
}
type WCaseFunc func(i int, cas WCase, ei EventInfo) error
func (w *W) ExpectAnyFunc(cases []WCase, fn WCaseFunc) {
UpdateWait() // Wait some time before starting the test.
Test:
for i, cas := range cases {
dbgprintf("ExpectAny: i=%d\n", i)
cas.Action()
Sync()
switch cas.Events {
case nil:
if ei := drainall(w.C); len(ei) != 0 {
w.Fatalf("unexpected dangling events: %v (i=%d)", ei, i)
}
default:
select {
case ei := <-w.C:
dbgprintf("received: path=%q, event=%v, sys=%v (i=%d)", ei.Path(),
ei.Event(), ei.Sys(), i)
for j, want := range cas.Events {
if err := EqualEventInfo(want, ei); err != nil {
dbgprint(err, j)
continue
}
if fn != nil {
if err := fn(i, cas, ei); err != nil {
w.Fatalf("ExpectAnyFunc(%d, %v)=%v", i, ei, err)
}
}
drainall(w.C) // TODO(rjeczalik): revisit
continue Test
}
w.Fatalf("ExpectAny received an event which does not match any of "+
"the expected ones (i=%d): want one of %v; got %v", i, cas.Events, ei)
case <-time.After(w.timeout()):
w.Fatalf("timed out after %v waiting for one of %v (i=%d)", w.timeout(),
cas.Events, i)
}
drainall(w.C) // TODO(rjeczalik): revisit
}
}
}
func (w *W) ExpectAny(cases []WCase) {
w.ExpectAnyFunc(cases, nil)
}
func (w *W) aggregate(ei []EventInfo, pf string) (evs map[string]Event) {
evs = make(map[string]Event)
for _, cas := range ei {
p := cas.Path()
if pf != "" {
p = filepath.Join(pf, p)
}
evs[p] |= cas.Event()
}
return
}
func (w *W) ExpectAllFunc(cases []WCase) {
UpdateWait() // Wait some time before starting the test.
for i, cas := range cases {
exp := w.aggregate(cas.Events, w.root)
dbgprintf("ExpectAll: i=%d\n", i)
cas.Action()
Sync()
got := w.aggregate(drainall(w.C), "")
for ep, ee := range exp {
ge, ok := got[ep]
if !ok {
w.Fatalf("missing events for %q (%v)", ep, ee)
continue
}
delete(got, ep)
if err := HasEventInfo(ee, ge, ep); err != nil {
w.Fatalf("ExpectAll received an event which does not match "+
"the expected ones for %q: want %v; got %v", ep, ee, ge)
continue
}
}
if len(got) != 0 {
w.Fatalf("ExpectAll received unexpected events: %v", got)
}
}
}
// ExpectAll requires all requested events to be send.
// It does not require events to be send in the same order or in the same
// chunks (e.g. NoteWrite and NoteExtend reported as independent events are
// treated the same as one NoteWrite|NoteExtend event).
func (w *W) ExpectAll(cases []WCase) {
w.ExpectAllFunc(cases)
}
// FuncType represents enums for Watcher interface.
type FuncType string
const (
FuncWatch = FuncType("Watch")
FuncUnwatch = FuncType("Unwatch")
FuncRewatch = FuncType("Rewatch")
FuncRecursiveWatch = FuncType("RecursiveWatch")
FuncRecursiveUnwatch = FuncType("RecursiveUnwatch")
FuncRecursiveRewatch = FuncType("RecursiveRewatch")
FuncStop = FuncType("Stop")
)
type Chans []chan EventInfo
func NewChans(n int) Chans {
ch := make([]chan EventInfo, n)
for i := range ch {
ch[i] = make(chan EventInfo, buffer)
}
return ch
}
func (c Chans) Foreach(fn func(chan<- EventInfo, node)) {
for i, ch := range c {
fn(ch, node{Name: strconv.Itoa(i)})
}
}
func (c Chans) Drain() (ei []EventInfo) {
n := len(c)
stop := make(chan struct{})
eich := make(chan EventInfo, n*buffer)
go func() {
defer close(eich)
cases := make([]reflect.SelectCase, n+1)
for i := range c {
cases[i].Chan = reflect.ValueOf(c[i])
cases[i].Dir = reflect.SelectRecv
}
cases[n].Chan = reflect.ValueOf(stop)
cases[n].Dir = reflect.SelectRecv
for {
i, v, ok := reflect.Select(cases)
if i == n {
return
}
if !ok {
panic("(Chans).Drain(): unexpected chan close")
}
eich <- v.Interface().(EventInfo)
}
}()
<-time.After(50 * time.Duration(n) * time.Millisecond)
close(stop)
for e := range eich {
ei = append(ei, e)
}
return
}
// Call represents single call to Watcher issued by the Tree
// and recorded by a spy Watcher mock.
type Call struct {
F FuncType // denotes type of function to call, for both watcher and notifier interface
C chan EventInfo // user channel being an argument to either Watch or Stop function
P string // regular Path argument and old path from RecursiveRewatch call
NP string // new Path argument from RecursiveRewatch call
E Event // regular Event argument and old Event from a Rewatch call
NE Event // new Event argument from Rewatch call
S interface{} // when Call is used as EventInfo, S is a value of Sys()
Dir bool // when Call is used as EventInfo, Dir is a value of isDir()
}
// Call implements the EventInfo interface.
func (c *Call) Event() Event { return c.E }
func (c *Call) Path() string { return c.P }
func (c *Call) String() string { return fmt.Sprintf("%#v", c) }
func (c *Call) Sys() interface{} { return c.S }
func (c *Call) isDir() (bool, error) { return c.Dir, nil }
// CallSlice is a convenient wrapper for a slice of Call values, which allows
// to sort them in ascending order.
type CallSlice []Call
// CallSlice implements sort.Interface inteface.
func (cs CallSlice) Len() int { return len(cs) }
func (cs CallSlice) Less(i, j int) bool { return cs[i].P < cs[j].P }
func (cs CallSlice) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] }
func (cs CallSlice) Sort() { sort.Sort(cs) }
// Spy is a mock for Watcher interface, which records every call.
type Spy []Call
func (s *Spy) Close() (_ error) { return }
func (s *Spy) Watch(p string, e Event) (_ error) {
dbgprintf("%s: (*Spy).Watch(%q, %v)", caller(), p, e)
*s = append(*s, Call{F: FuncWatch, P: p, E: e})
return
}
func (s *Spy) Unwatch(p string) (_ error) {
dbgprintf("%s: (*Spy).Unwatch(%q)", caller(), p)
*s = append(*s, Call{F: FuncUnwatch, P: p})
return
}
func (s *Spy) Rewatch(p string, olde, newe Event) (_ error) {
dbgprintf("%s: (*Spy).Rewatch(%q, %v, %v)", caller(), p, olde, newe)
*s = append(*s, Call{F: FuncRewatch, P: p, E: olde, NE: newe})
return
}
func (s *Spy) RecursiveWatch(p string, e Event) (_ error) {
dbgprintf("%s: (*Spy).RecursiveWatch(%q, %v)", caller(), p, e)
*s = append(*s, Call{F: FuncRecursiveWatch, P: p, E: e})
return
}
func (s *Spy) RecursiveUnwatch(p string) (_ error) {
dbgprintf("%s: (*Spy).RecursiveUnwatch(%q)", caller(), p)
*s = append(*s, Call{F: FuncRecursiveUnwatch, P: p})
return
}
func (s *Spy) RecursiveRewatch(oldp, newp string, olde, newe Event) (_ error) {
dbgprintf("%s: (*Spy).RecursiveRewatch(%q, %q, %v, %v)", caller(), oldp, newp, olde, newe)
*s = append(*s, Call{F: FuncRecursiveRewatch, P: oldp, NP: newp, E: olde, NE: newe})
return
}
type RCase struct {
Call Call
Record []Call
}
type TCase struct {
Event Call
Receiver Chans
}
type NCase struct {
Event WCase
Receiver Chans
}
type N struct {
Timeout time.Duration
t *testing.T
tree tree
w *W
spy *Spy
c chan EventInfo
j int // spy offset
realroot string
}
func newN(t *testing.T, tree string) *N {
n := &N{
t: t,
w: newWatcherTest(t, tree),
}
realroot, err := canonical(n.w.root)
if err != nil {
t.Fatalf("%s: unexpected fixture failure: %v", caller(), err)
}
n.realroot = realroot
return n
}
func newTreeN(t *testing.T, tree string) *N {
c := make(chan EventInfo, buffer)
n := newN(t, tree)
n.spy = &Spy{}
n.w.Watcher = n.spy
n.w.C = c
n.c = c
return n
}
func NewNotifyTest(t *testing.T, tree string) *N {
n := newN(t, tree)
if rw, ok := n.w.watcher().(recursiveWatcher); ok {
n.tree = newRecursiveTree(rw, n.w.c())
} else {
n.tree = newNonrecursiveTree(n.w.watcher(), n.w.c(), nil)
}
return n
}
func NewRecursiveTreeTest(t *testing.T, tree string) *N {
n := newTreeN(t, tree)
n.tree = newRecursiveTree(n.spy, n.c)
return n
}
func NewNonrecursiveTreeTest(t *testing.T, tree string) *N {
n := newTreeN(t, tree)
n.tree = newNonrecursiveTree(n.spy, n.c, nil)
return n
}
func NewNonrecursiveTreeTestC(t *testing.T, tree string) (*N, chan EventInfo) {
rec := make(chan EventInfo, buffer)
recinternal := make(chan EventInfo, buffer)
recuser := make(chan EventInfo, buffer)
go func() {
for ei := range rec {
select {
case recinternal <- ei:
default:
t.Fatalf("failed to send ei to recinternal: not ready")
}
select {
case recuser <- ei:
default:
t.Fatalf("failed to send ei to recuser: not ready")
}
}
}()
n := newTreeN(t, tree)
tr := newNonrecursiveTree(n.spy, n.c, recinternal)
tr.rec = rec
n.tree = tr
return n, recuser
}
func (n *N) timeout() time.Duration {
if n.Timeout != 0 {
return n.Timeout
}
return n.w.timeout()
}
func (n *N) W() *W {
return n.w
}
func (n *N) Close() error {
defer os.RemoveAll(n.w.root)
if err := n.tree.Close(); err != nil {
n.w.Fatalf("(notifier).Close()=%v", err)
}
return nil
}
func (n *N) Watch(path string, c chan<- EventInfo, events ...Event) {
UpdateWait() // we need to wait on Windows because of its asynchronous watcher.
path = filepath.Join(n.w.root, path)
if err := n.tree.Watch(path, c, events...); err != nil {
n.t.Errorf("Watch(%s, %p, %v)=%v", path, c, events, err)
}
}
func (n *N) WatchErr(path string, c chan<- EventInfo, err error, events ...Event) {
path = filepath.Join(n.w.root, path)
switch e := n.tree.Watch(path, c, events...); {
case err == nil && e == nil:
n.t.Errorf("Watch(%s, %p, %v)=nil", path, c, events)
case err != nil && e != err:
n.t.Errorf("Watch(%s, %p, %v)=%v != %v", path, c, events, e, err)
}
}
func (n *N) Stop(c chan<- EventInfo) {
n.tree.Stop(c)
}
func (n *N) Call(calls ...Call) {
for i := range calls {
switch calls[i].F {
case FuncWatch:
n.Watch(calls[i].P, calls[i].C, calls[i].E)
case FuncStop:
n.Stop(calls[i].C)
default:
panic("unsupported call type: " + string(calls[i].F))
}
}
}
func (n *N) expectDry(ch Chans, i int) {
if ei := ch.Drain(); len(ei) != 0 {
n.w.Fatalf("unexpected dangling events: %v (i=%d)", ei, i)
}
}
func (n *N) ExpectRecordedCalls(cases []RCase) {
for i, cas := range cases {
dbgprintf("ExpectRecordedCalls: i=%d\n", i)
n.Call(cas.Call)
record := (*n.spy)[n.j:]
if len(cas.Record) == 0 && len(record) == 0 {
continue
}
n.j = len(*n.spy)
if len(record) != len(cas.Record) {
n.t.Fatalf("%s: want len(record)=%d; got %d [%+v] (i=%d)", caller(),
len(cas.Record), len(record), record, i)
}
CallSlice(record).Sort()
for j := range cas.Record {
if err := EqualCall(cas.Record[j], record[j]); err != nil {
n.t.Fatalf("%s: %v (i=%d, j=%d)", caller(), err, i, j)
}
}
}
}
func (n *N) collect(ch Chans) <-chan []EventInfo {
done := make(chan []EventInfo)
go func() {
cases := make([]reflect.SelectCase, len(ch))
unique := make(map[<-chan EventInfo]EventInfo, len(ch))
for i := range ch {
cases[i].Chan = reflect.ValueOf(ch[i])
cases[i].Dir = reflect.SelectRecv
}
for i := len(cases); i != 0; i = len(cases) {
j, v, ok := reflect.Select(cases)
if !ok {
n.t.Fatal("unexpected chan close")
}
ch := cases[j].Chan.Interface().(chan EventInfo)
got := v.Interface().(EventInfo)
if ei, ok := unique[ch]; ok {
n.t.Fatalf("duplicated event %v (previous=%v) received on collect", got, ei)
}
unique[ch] = got
cases[j], cases = cases[i-1], cases[:i-1]
}
collected := make([]EventInfo, 0, len(ch))
for _, ch := range unique {
collected = append(collected, ch)
}
done <- collected
}()
return done
}
func (n *N) abs(rel Call) *Call {
rel.P = filepath.Join(n.realroot, filepath.FromSlash(rel.P))
if !filepath.IsAbs(rel.P) {
rel.P = filepath.Join(wd, rel.P)
}
return &rel
}
func (n *N) ExpectTreeEvents(cases []TCase, all Chans) {
for i, cas := range cases {
dbgprintf("ExpectTreeEvents: i=%d\n", i)
// Ensure there're no dangling event left by previous test-case.
n.expectDry(all, i)
n.c <- n.abs(cas.Event)
switch cas.Receiver {
case nil:
n.expectDry(all, i)
default:
ch := n.collect(cas.Receiver)
select {
case collected := <-ch:
for _, got := range collected {
if err := EqualEventInfo(&cas.Event, got); err != nil {
n.w.Fatalf("%s: %s (i=%d)", caller(), err, i)
}
}
case <-time.After(n.timeout()):
n.w.Fatalf("ExpectTreeEvents has timed out after %v waiting for"+
" %v on %s (i=%d)", n.timeout(), cas.Event.E, cas.Event.P, i)
}
}
}
n.expectDry(all, -1)
}
func (n *N) ExpectNotifyEvents(cases []NCase, all Chans) {
UpdateWait() // Wait some time before starting the test.
for i, cas := range cases {
dbgprintf("ExpectNotifyEvents: i=%d\n", i)
cas.Event.Action()
Sync()
switch cas.Receiver {
case nil:
n.expectDry(all, i)
default:
ch := n.collect(cas.Receiver)
select {
case collected := <-ch:
Compare:
for j, ei := range collected {
dbgprintf("received: path=%q, event=%v, sys=%v (i=%d, j=%d)", ei.Path(),
ei.Event(), ei.Sys(), i, j)
for _, want := range cas.Event.Events {
if err := EqualEventInfo(want, ei); err != nil {
dbgprint(err, j)
continue
}
continue Compare
}
n.w.Fatalf("ExpectNotifyEvents received an event which does not"+
" match any of the expected ones (i=%d): want one of %v; got %v", i,
cas.Event.Events, ei)
}
case <-time.After(n.timeout()):
n.w.Fatalf("ExpectNotifyEvents did not receive any of the expected events [%v] "+
"after %v (i=%d)", cas.Event, n.timeout(), i)
}
}
}
n.expectDry(all, -1)
}
func (n *N) Walk(fn walkFunc) {
switch t := n.tree.(type) {
case *recursiveTree:
if err := t.root.Walk("", fn); err != nil {
n.w.Fatal(err)
}
case *nonrecursiveTree:
if err := t.root.Walk("", fn); err != nil {
n.w.Fatal(err)
}
default:
n.t.Fatal("unknown tree type")
}
}
+22
View File
@@ -0,0 +1,22 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
const buffer = 128
type tree interface {
Watch(string, chan<- EventInfo, ...Event) error
Stop(chan<- EventInfo)
Close() error
}
func newTree() tree {
c := make(chan EventInfo, buffer)
w := newWatcher(c)
if rw, ok := w.(recursiveWatcher); ok {
return newRecursiveTree(rw, c)
}
return newNonrecursiveTree(w, c, make(chan EventInfo, buffer))
}
+292
View File
@@ -0,0 +1,292 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
import "sync"
// nonrecursiveTree TODO(rjeczalik)
type nonrecursiveTree struct {
rw sync.RWMutex // protects root
root root
w watcher
c chan EventInfo
rec chan EventInfo
}
// newNonrecursiveTree TODO(rjeczalik)
func newNonrecursiveTree(w watcher, c, rec chan EventInfo) *nonrecursiveTree {
if rec == nil {
rec = make(chan EventInfo, buffer)
}
t := &nonrecursiveTree{
root: root{nd: newnode("")},
w: w,
c: c,
rec: rec,
}
go t.dispatch(c)
go t.internal(rec)
return t
}
// dispatch TODO(rjeczalik)
func (t *nonrecursiveTree) dispatch(c <-chan EventInfo) {
for ei := range c {
dbgprintf("dispatching %v on %q", ei.Event(), ei.Path())
go func(ei EventInfo) {
var nd node
var isrec bool
dir, base := split(ei.Path())
fn := func(it node, isbase bool) error {
isrec = isrec || it.Watch.IsRecursive()
if isbase {
nd = it
} else {
it.Watch.Dispatch(ei, recursive)
}
return nil
}
t.rw.RLock()
// Notify recursive watchpoints found on the path.
if err := t.root.WalkPath(dir, fn); err != nil {
dbgprint("dispatch did not reach leaf:", err)
t.rw.RUnlock()
return
}
// Notify parent watchpoint.
nd.Watch.Dispatch(ei, 0)
isrec = isrec || nd.Watch.IsRecursive()
// If leaf watchpoint exists, notify it.
if nd, ok := nd.Child[base]; ok {
isrec = isrec || nd.Watch.IsRecursive()
nd.Watch.Dispatch(ei, 0)
}
t.rw.RUnlock()
// If the event describes newly leaf directory created within
if !isrec || ei.Event() != Create {
return
}
if ok, err := ei.(isDirer).isDir(); !ok || err != nil {
return
}
t.rec <- ei
}(ei)
}
}
// internal TODO(rjeczalik)
func (t *nonrecursiveTree) internal(rec <-chan EventInfo) {
for ei := range rec {
var nd node
var eset = internal
t.rw.Lock()
t.root.WalkPath(ei.Path(), func(it node, _ bool) error {
if e := it.Watch[t.rec]; e != 0 && e > eset {
eset = e
}
nd = it
return nil
})
if eset == internal {
t.rw.Unlock()
continue
}
err := nd.Add(ei.Path()).AddDir(t.recFunc(eset))
t.rw.Unlock()
if err != nil {
dbgprintf("internal(%p) error: %v", rec, err)
}
}
}
// watchAdd TODO(rjeczalik)
func (t *nonrecursiveTree) watchAdd(nd node, c chan<- EventInfo, e Event) eventDiff {
if e&recursive != 0 {
diff := nd.Watch.Add(t.rec, e|Create|omit)
nd.Watch.Add(c, e)
return diff
}
return nd.Watch.Add(c, e)
}
// watchDelMin TODO(rjeczalik)
func (t *nonrecursiveTree) watchDelMin(min Event, nd node, c chan<- EventInfo, e Event) eventDiff {
old, ok := nd.Watch[t.rec]
if ok {
nd.Watch[t.rec] = min
}
diff := nd.Watch.Del(c, e)
if ok {
switch old &^= diff[0] &^ diff[1]; {
case old|internal == internal:
delete(nd.Watch, t.rec)
if set, ok := nd.Watch[nil]; ok && len(nd.Watch) == 1 && set == 0 {
delete(nd.Watch, nil)
}
default:
nd.Watch.Add(t.rec, old|Create)
switch {
case diff == none:
case diff[1]|Create == diff[0]:
diff = none
default:
diff[1] |= Create
}
}
}
return diff
}
// watchDel TODO(rjeczalik)
func (t *nonrecursiveTree) watchDel(nd node, c chan<- EventInfo, e Event) eventDiff {
return t.watchDelMin(0, nd, c, e)
}
// Watch TODO(rjeczalik)
func (t *nonrecursiveTree) Watch(path string, c chan<- EventInfo, events ...Event) error {
if c == nil {
panic("notify: Watch using nil channel")
}
// Expanding with empty event set is a nop.
if len(events) == 0 {
return nil
}
path, isrec, err := cleanpath(path)
if err != nil {
return err
}
eset := joinevents(events)
t.rw.Lock()
defer t.rw.Unlock()
nd := t.root.Add(path)
if isrec {
return t.watchrec(nd, c, eset|recursive)
}
return t.watch(nd, c, eset)
}
func (t *nonrecursiveTree) watch(nd node, c chan<- EventInfo, e Event) (err error) {
diff := nd.Watch.Add(c, e)
switch {
case diff == none:
return nil
case diff[1] == 0:
// TODO(rjeczalik): cleanup this panic after implementation is stable
panic("eset is empty: " + nd.Name)
case diff[0] == 0:
err = t.w.Watch(nd.Name, diff[1])
default:
err = t.w.Rewatch(nd.Name, diff[0], diff[1])
}
if err != nil {
nd.Watch.Del(c, diff.Event())
return err
}
return nil
}
func (t *nonrecursiveTree) recFunc(e Event) walkFunc {
return func(nd node) error {
switch diff := nd.Watch.Add(t.rec, e|omit|Create); {
case diff == none:
case diff[1] == 0:
// TODO(rjeczalik): cleanup this panic after implementation is stable
panic("eset is empty: " + nd.Name)
case diff[0] == 0:
t.w.Watch(nd.Name, diff[1])
default:
t.w.Rewatch(nd.Name, diff[0], diff[1])
}
return nil
}
}
func (t *nonrecursiveTree) watchrec(nd node, c chan<- EventInfo, e Event) error {
var traverse func(walkFunc) error
// Non-recursive tree listens on Create event for every recursive
// watchpoint in order to automagically set a watch for every
// created directory.
switch diff := nd.Watch.dryAdd(t.rec, e|Create); {
case diff == none:
t.watchAdd(nd, c, e)
nd.Watch.Add(t.rec, e|omit|Create)
return nil
case diff[1] == 0:
// TODO(rjeczalik): cleanup this panic after implementation is stable
panic("eset is empty: " + nd.Name)
case diff[0] == 0:
// TODO(rjeczalik): BFS into directories and skip subtree as soon as first
// recursive watchpoint is encountered.
traverse = nd.AddDir
default:
traverse = nd.Walk
}
// TODO(rjeczalik): account every path that failed to be (re)watched
// and retry.
if err := traverse(t.recFunc(e)); err != nil {
return err
}
t.watchAdd(nd, c, e)
return nil
}
type walkWatchpointFunc func(Event, node) error
func (t *nonrecursiveTree) walkWatchpoint(nd node, fn walkWatchpointFunc) error {
type minode struct {
min Event
nd node
}
mnd := minode{nd: nd}
stack := []minode{mnd}
Traverse:
for n := len(stack); n != 0; n = len(stack) {
mnd, stack = stack[n-1], stack[:n-1]
// There must be no recursive watchpoints if the node has no watchpoints
// itself (every node in subtree rooted at recursive watchpoints must
// have at least nil (total) and t.rec watchpoints).
if len(mnd.nd.Watch) != 0 {
switch err := fn(mnd.min, mnd.nd); err {
case nil:
case errSkip:
continue Traverse
default:
return err
}
}
for _, nd := range mnd.nd.Child {
stack = append(stack, minode{mnd.nd.Watch[t.rec], nd})
}
}
return nil
}
// Stop TODO(rjeczalik)
func (t *nonrecursiveTree) Stop(c chan<- EventInfo) {
fn := func(min Event, nd node) error {
// TODO(rjeczalik): aggregate watcher errors and retry; in worst case
// forward to the user.
switch diff := t.watchDelMin(min, nd, c, all); {
case diff == none:
return nil
case diff[1] == 0:
t.w.Unwatch(nd.Name)
default:
t.w.Rewatch(nd.Name, diff[0], diff[1])
}
return nil
}
t.rw.Lock()
err := t.walkWatchpoint(t.root.nd, fn) // TODO(rjeczalik): store max root per c
t.rw.Unlock()
dbgprintf("Stop(%p) error: %v\n", c, err)
}
// Close TODO(rjeczalik)
func (t *nonrecursiveTree) Close() error {
err := t.w.Close()
close(t.c)
return err
}
+543
View File
@@ -0,0 +1,543 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
import (
"fmt"
"testing"
)
func TestNonrecursiveTree(t *testing.T) {
n := NewNonrecursiveTreeTest(t, "testdata/vfs.txt")
defer n.Close()
ch := NewChans(5)
watches := [...]RCase{
// i=0
{
Call: Call{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/fs.go",
C: ch[0],
E: Rename,
},
Record: []Call{
{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/fs.go",
E: Rename,
},
},
},
// i=1
{
Call: Call{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/cmd/...",
C: ch[1],
E: Remove,
},
Record: []Call{
{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/cmd",
E: Create | Remove,
},
{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/cmd/gotree",
E: Create | Remove,
},
{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/cmd/mktree",
E: Create | Remove,
},
},
},
// i=2
{
Call: Call{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/cmd/...",
C: ch[2],
E: Rename,
},
Record: []Call{
{
F: FuncRewatch,
P: "src/github.com/rjeczalik/fs/cmd",
E: Create | Remove,
NE: Create | Remove | Rename,
},
{
F: FuncRewatch,
P: "src/github.com/rjeczalik/fs/cmd/gotree",
E: Create | Remove,
NE: Create | Remove | Rename,
},
{
F: FuncRewatch,
P: "src/github.com/rjeczalik/fs/cmd/mktree",
E: Create | Remove,
NE: Create | Remove | Rename,
},
},
},
// i=3
{
Call: Call{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/cmd/mktree/...",
C: ch[2],
E: Write,
},
Record: []Call{
{
F: FuncRewatch,
P: "src/github.com/rjeczalik/fs/cmd/mktree",
E: Create | Remove | Rename,
NE: Create | Remove | Rename | Write,
},
},
},
// i=4
{
Call: Call{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu/include",
C: ch[3],
E: Create,
},
Record: []Call{
{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu/include",
E: Create,
},
},
},
// i=5
{
Call: Call{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu/include/qttu/detail/...",
C: ch[3],
E: Write,
},
Record: []Call{
{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu/include/qttu/detail",
E: Create | Write,
},
},
},
// i=6
{
Call: Call{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu/include/...",
C: ch[0],
E: Rename,
},
Record: []Call{
{
F: FuncRewatch,
P: "src/github.com/pblaszczyk/qttu/include",
E: Create,
NE: Create | Rename,
},
{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu/include/qttu",
E: Create | Rename,
},
{
F: FuncRewatch,
P: "src/github.com/pblaszczyk/qttu/include/qttu/detail",
E: Create | Write,
NE: Create | Write | Rename,
},
},
},
// i=7
{
Call: Call{
F: FuncWatch,
P: "src/github.com/pblaszczyk/...",
C: ch[1],
E: Write,
},
Record: []Call{
{
F: FuncWatch,
P: "src/github.com/pblaszczyk",
E: Create | Write,
},
{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu",
E: Create | Write,
},
{
F: FuncRewatch,
P: "src/github.com/pblaszczyk/qttu/include",
E: Create | Rename,
NE: Create | Rename | Write,
},
{
F: FuncRewatch,
P: "src/github.com/pblaszczyk/qttu/include/qttu",
E: Create | Rename,
NE: Create | Rename | Write,
},
{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu/src",
E: Create | Write,
},
},
},
// i=8
{
Call: Call{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu/include/...",
C: ch[4],
E: Write,
},
Record: nil,
},
// i=9
{
Call: Call{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu",
C: ch[3],
E: Remove,
},
Record: []Call{
{
F: FuncRewatch,
P: "src/github.com/pblaszczyk/qttu",
E: Create | Write,
NE: Create | Write | Remove,
},
},
},
}
n.ExpectRecordedCalls(watches[:])
events := [...]TCase{
// i=0
{
Event: Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Rename},
Receiver: Chans{ch[0]},
},
// i=1
{
Event: Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Create},
Receiver: nil,
},
// i=2
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/cmd.go", E: Remove},
Receiver: Chans{ch[1]},
},
// i=3
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/doc.go", E: Write},
Receiver: nil,
},
// i=4
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/mktree/main.go", E: Write},
Receiver: Chans{ch[2]},
},
// i=5
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/mktree/tree.go", E: Create},
Receiver: nil,
},
// i=6
{
Event: Call{P: "src/github.com/pblaszczyk/qttu/include/.lock", E: Create},
Receiver: Chans{ch[3]},
},
// i=7
{
Event: Call{P: "src/github.com/pblaszczyk/qttu/include/qttu/detail/registry.hh", E: Write},
Receiver: Chans{ch[3], ch[1], ch[4]},
},
// i=8
{
Event: Call{P: "src/github.com/pblaszczyk/qttu/include/qttu", E: Remove},
Receiver: nil,
},
// i=9
{
Event: Call{P: "src/github.com/pblaszczyk/qttu/include", E: Remove},
Receiver: Chans{ch[3]},
},
}
n.ExpectTreeEvents(events[:], ch)
stops := [...]RCase{
// i=0
{
Call: Call{
F: FuncStop,
C: ch[4],
},
Record: nil,
},
// i=1
{
Call: Call{
F: FuncStop,
C: ch[3],
},
Record: []Call{
{
F: FuncRewatch,
P: "src/github.com/pblaszczyk/qttu",
E: Create | Write | Remove,
NE: Create | Write,
},
},
},
// i=2
{
Call: Call{
F: FuncStop,
C: ch[2],
},
Record: []Call{
{
F: FuncRewatch,
P: "src/github.com/rjeczalik/fs/cmd",
E: Create | Remove | Rename,
NE: Create | Remove,
},
{
F: FuncRewatch,
P: "src/github.com/rjeczalik/fs/cmd/gotree",
E: Create | Remove | Rename,
NE: Create | Remove,
},
{
F: FuncRewatch,
P: "src/github.com/rjeczalik/fs/cmd/mktree",
E: Create | Remove | Rename | Write,
NE: Create | Remove,
},
},
},
// i=3
{
Call: Call{
F: FuncStop,
C: ch[1],
},
Record: []Call{
{
F: FuncUnwatch,
P: "src/github.com/pblaszczyk",
},
{
F: FuncUnwatch,
P: "src/github.com/pblaszczyk/qttu",
},
{
F: FuncRewatch,
P: "src/github.com/pblaszczyk/qttu/include",
E: Create | Rename | Write,
NE: Create | Rename,
},
{
F: FuncRewatch,
P: "src/github.com/pblaszczyk/qttu/include/qttu",
E: Create | Rename | Write,
NE: Create | Rename,
},
{
F: FuncRewatch,
P: "src/github.com/pblaszczyk/qttu/include/qttu/detail",
E: Create | Rename | Write,
NE: Create | Rename,
},
{
F: FuncUnwatch,
P: "src/github.com/pblaszczyk/qttu/src",
},
{
F: FuncUnwatch,
P: "src/github.com/rjeczalik/fs/cmd",
},
{
F: FuncUnwatch,
P: "src/github.com/rjeczalik/fs/cmd/gotree",
},
{
F: FuncUnwatch,
P: "src/github.com/rjeczalik/fs/cmd/mktree",
},
},
},
// i=4
{
Call: Call{
F: FuncStop,
C: ch[0],
},
Record: []Call{
{
F: FuncUnwatch,
P: "src/github.com/pblaszczyk/qttu/include",
},
{
F: FuncUnwatch,
P: "src/github.com/pblaszczyk/qttu/include/qttu",
},
{
F: FuncUnwatch,
P: "src/github.com/pblaszczyk/qttu/include/qttu/detail",
},
{
F: FuncUnwatch,
P: "src/github.com/rjeczalik/fs/fs.go",
},
},
},
}
n.ExpectRecordedCalls(stops[:])
n.Walk(func(nd node) error {
if len(nd.Watch) != 0 {
return fmt.Errorf("unexpected watchpoint: name=%s, eventset=%v (len=%d)",
nd.Name, nd.Watch.Total(), len(nd.Watch))
}
return nil
})
}
func TestNonrecursiveTreeInternal(t *testing.T) {
n, c := NewNonrecursiveTreeTestC(t, "testdata/vfs.txt")
defer n.Close()
ch := NewChans(5)
watches := [...]RCase{
// i=0
{
Call: Call{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/cmd/...",
C: ch[0],
E: Remove,
},
Record: []Call{
{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/cmd",
E: Create | Remove,
},
{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/cmd/gotree",
E: Create | Remove,
},
{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/cmd/mktree",
E: Create | Remove,
},
},
},
// i=1
{
Call: Call{
F: FuncWatch,
P: "src/github.com/ppknap/link/include/coost/...",
C: ch[1],
E: Create,
},
Record: []Call{
{
F: FuncWatch,
P: "src/github.com/ppknap/link/include/coost",
E: Create,
},
{
F: FuncWatch,
P: "src/github.com/ppknap/link/include/coost/link",
E: Create,
},
{
F: FuncWatch,
P: "src/github.com/ppknap/link/include/coost/link/detail",
E: Create,
},
{
F: FuncWatch,
P: "src/github.com/ppknap/link/include/coost/link/detail/stdhelpers",
E: Create,
},
},
},
}
n.ExpectRecordedCalls(watches[:])
events := [...]TCase{
// i=0
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/dir", E: Create, Dir: true},
Receiver: Chans{c},
},
// i=1
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/dir/another", E: Create, Dir: true},
Receiver: Chans{c},
},
// i=2
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/file", E: Create, Dir: false},
Receiver: nil,
},
// i=3
{
Event: Call{P: "src/github.com/ppknap/link/include/coost/dir", E: Create, Dir: true},
Receiver: Chans{ch[1], c},
},
// i=4
{
Event: Call{P: "src/github.com/ppknap/link/include/coost/dir/another", E: Create, Dir: true},
Receiver: Chans{ch[1], c},
},
// i=5
{
Event: Call{P: "src/github.com/ppknap/link/include/coost/file", E: Create, Dir: false},
Receiver: Chans{ch[1]},
},
// i=6
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/mktree", E: Remove},
Receiver: Chans{ch[0]},
},
// i=7
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/rmtree", E: Create, Dir: true},
Receiver: Chans{c},
},
}
n.ExpectTreeEvents(events[:], ch)
}
+354
View File
@@ -0,0 +1,354 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
import "sync"
// watchAdd TODO(rjeczalik)
func watchAdd(nd node, c chan<- EventInfo, e Event) eventDiff {
diff := nd.Watch.Add(c, e)
if wp := nd.Child[""].Watch; len(wp) != 0 {
e = wp.Total()
diff[0] |= e
diff[1] |= e
if diff[0] == diff[1] {
return none
}
}
return diff
}
// watchAddInactive TODO(rjeczalik)
func watchAddInactive(nd node, c chan<- EventInfo, e Event) eventDiff {
wp := nd.Child[""].Watch
if wp == nil {
wp = make(watchpoint)
nd.Child[""] = node{Watch: wp}
}
diff := wp.Add(c, e)
e = nd.Watch.Total()
diff[0] |= e
diff[1] |= e
if diff[0] == diff[1] {
return none
}
return diff
}
// watchCopy TODO(rjeczalik)
func watchCopy(src, dst node) {
for c, e := range src.Watch {
if c == nil {
continue
}
watchAddInactive(dst, c, e)
}
if wpsrc := src.Child[""].Watch; len(wpsrc) != 0 {
wpdst := dst.Child[""].Watch
for c, e := range wpsrc {
if c == nil {
continue
}
wpdst.Add(c, e)
}
}
}
// watchDel TODO(rjeczalik)
func watchDel(nd node, c chan<- EventInfo, e Event) eventDiff {
diff := nd.Watch.Del(c, e)
if wp := nd.Child[""].Watch; len(wp) != 0 {
diffInactive := wp.Del(c, e)
e = wp.Total()
// TODO(rjeczalik): add e if e != all?
diff[0] |= diffInactive[0] | e
diff[1] |= diffInactive[1] | e
if diff[0] == diff[1] {
return none
}
}
return diff
}
// watchTotal TODO(rjeczalik)
func watchTotal(nd node) Event {
e := nd.Watch.Total()
if wp := nd.Child[""].Watch; len(wp) != 0 {
e |= wp.Total()
}
return e
}
// watchIsRecursive TODO(rjeczalik)
func watchIsRecursive(nd node) bool {
ok := nd.Watch.IsRecursive()
// TODO(rjeczalik): add a test for len(wp) != 0 change the condition.
if wp := nd.Child[""].Watch; len(wp) != 0 {
// If a watchpoint holds inactive watchpoints, it means it's a parent
// one, which is recursive by nature even though it may be not recursive
// itself.
ok = true
}
return ok
}
// recursiveTree TODO(rjeczalik)
type recursiveTree struct {
rw sync.RWMutex // protects root
root root
// TODO(rjeczalik): merge watcher + recursiveWatcher after #5 and #6
w interface {
watcher
recursiveWatcher
}
c chan EventInfo
}
// newRecursiveTree TODO(rjeczalik)
func newRecursiveTree(w recursiveWatcher, c chan EventInfo) *recursiveTree {
t := &recursiveTree{
root: root{nd: newnode("")},
w: struct {
watcher
recursiveWatcher
}{w.(watcher), w},
c: c,
}
go t.dispatch()
return t
}
// dispatch TODO(rjeczalik)
func (t *recursiveTree) dispatch() {
for ei := range t.c {
dbgprintf("dispatching %v on %q", ei.Event(), ei.Path())
go func(ei EventInfo) {
nd, ok := node{}, false
dir, base := split(ei.Path())
fn := func(it node, isbase bool) error {
if isbase {
nd = it
} else {
it.Watch.Dispatch(ei, recursive)
}
return nil
}
t.rw.RLock()
defer t.rw.RUnlock()
// Notify recursive watchpoints found on the path.
if err := t.root.WalkPath(dir, fn); err != nil {
dbgprint("dispatch did not reach leaf:", err)
return
}
// Notify parent watchpoint.
nd.Watch.Dispatch(ei, 0)
// If leaf watchpoint exists, notify it.
if nd, ok = nd.Child[base]; ok {
nd.Watch.Dispatch(ei, 0)
}
}(ei)
}
}
// Watch TODO(rjeczalik)
func (t *recursiveTree) Watch(path string, c chan<- EventInfo, events ...Event) error {
if c == nil {
panic("notify: Watch using nil channel")
}
// Expanding with empty event set is a nop.
if len(events) == 0 {
return nil
}
path, isrec, err := cleanpath(path)
if err != nil {
return err
}
eventset := joinevents(events)
if isrec {
eventset |= recursive
}
t.rw.Lock()
defer t.rw.Unlock()
// case 1: cur is a child
//
// Look for parent watch which already covers the given path.
parent := node{}
self := false
err = t.root.WalkPath(path, func(nd node, isbase bool) error {
if watchTotal(nd) != 0 {
parent = nd
self = isbase
return errSkip
}
return nil
})
cur := t.root.Add(path) // add after the walk, so it's less to traverse
if err == nil && parent.Watch != nil {
// Parent watch found. Register inactive watchpoint, so we have enough
// information to shrink the eventset on eventual Stop.
// return t.resetwatchpoint(parent, parent, c, eventset|inactive)
var diff eventDiff
if self {
diff = watchAdd(cur, c, eventset)
} else {
diff = watchAddInactive(parent, c, eventset)
}
switch {
case diff == none:
// the parent watchpoint already covers requested subtree with its
// eventset
case diff[0] == 0:
// TODO(rjeczalik): cleanup this panic after implementation is stable
panic("dangling watchpoint: " + parent.Name)
default:
if isrec || watchIsRecursive(parent) {
err = t.w.RecursiveRewatch(parent.Name, parent.Name, diff[0], diff[1])
} else {
err = t.w.Rewatch(parent.Name, diff[0], diff[1])
}
if err != nil {
watchDel(parent, c, diff.Event())
return err
}
watchAdd(cur, c, eventset)
// TODO(rjeczalik): account top-most path for c
return nil
}
if !self {
watchAdd(cur, c, eventset)
}
return nil
}
// case 2: cur is new parent
//
// Look for children nodes, unwatch n-1 of them and rewatch the last one.
var children []node
fn := func(nd node) error {
if len(nd.Watch) == 0 {
return nil
}
children = append(children, nd)
return errSkip
}
switch must(cur.Walk(fn)); len(children) {
case 0:
// no child watches, cur holds a new watch
case 1:
watchAdd(cur, c, eventset) // TODO(rjeczalik): update cache c subtree root?
watchCopy(children[0], cur)
err = t.w.RecursiveRewatch(children[0].Name, cur.Name, watchTotal(children[0]),
watchTotal(cur))
if err != nil {
// Clean inactive watchpoint. The c chan did not exist before.
cur.Child[""] = node{}
delete(cur.Watch, c)
return err
}
return nil
default:
watchAdd(cur, c, eventset)
// Copy children inactive watchpoints to the new parent.
for _, nd := range children {
watchCopy(nd, cur)
}
// Watch parent subtree.
if err = t.w.RecursiveWatch(cur.Name, watchTotal(cur)); err != nil {
// Clean inactive watchpoint. The c chan did not exist before.
cur.Child[""] = node{}
delete(cur.Watch, c)
return err
}
// Unwatch children subtrees.
var e error
for _, nd := range children {
if watchIsRecursive(nd) {
e = t.w.RecursiveUnwatch(nd.Name)
} else {
e = t.w.Unwatch(nd.Name)
}
if e != nil {
err = nonil(err, e)
// TODO(rjeczalik): child is still watched, warn all its watchpoints
// about possible duplicate events via Error event
}
}
return err
}
// case 3: cur is new, alone node
switch diff := watchAdd(cur, c, eventset); {
case diff == none:
// TODO(rjeczalik): cleanup this panic after implementation is stable
panic("watch requested but no parent watchpoint found: " + cur.Name)
case diff[0] == 0:
if isrec {
err = t.w.RecursiveWatch(cur.Name, diff[1])
} else {
err = t.w.Watch(cur.Name, diff[1])
}
if err != nil {
watchDel(cur, c, diff.Event())
return err
}
default:
// TODO(rjeczalik): cleanup this panic after implementation is stable
panic("watch requested but no parent watchpoint found: " + cur.Name)
}
return nil
}
// Stop TODO(rjeczalik)
//
// TODO(rjeczalik): Split parent watchpoint - transfer watches to children
// if parent is no longer needed. This carries a risk that underlying
// watcher calls could fail - reconsider if it's worth the effort.
func (t *recursiveTree) Stop(c chan<- EventInfo) {
var err error
fn := func(nd node) (e error) {
diff := watchDel(nd, c, all)
switch {
case diff == none && watchTotal(nd) == 0:
// TODO(rjeczalik): There's no watchpoints deeper in the tree,
// probably we should remove the nodes as well.
return nil
case diff == none:
// Removing c from nd does not require shrinking its eventset.
case diff[1] == 0:
if watchIsRecursive(nd) {
e = t.w.RecursiveUnwatch(nd.Name)
} else {
e = t.w.Unwatch(nd.Name)
}
default:
if watchIsRecursive(nd) {
e = t.w.RecursiveRewatch(nd.Name, nd.Name, diff[0], diff[1])
} else {
e = t.w.Rewatch(nd.Name, diff[0], diff[1])
}
}
fn := func(nd node) error {
watchDel(nd, c, all)
return nil
}
err = nonil(err, e, nd.Walk(fn))
// TODO(rjeczalik): if e != nil store dummy chan in nd.Watch just to
// retry un/rewatching next time and/or let the user handle the failure
// vie Error event?
return errSkip
}
t.rw.Lock()
e := t.root.Walk("", fn) // TODO(rjeczalik): use max root per c
t.rw.Unlock()
if e != nil {
err = nonil(err, e)
}
dbgprintf("Stop(%p) error: %v\n", c, err)
}
// Close TODO(rjeczalik)
func (t *recursiveTree) Close() error {
err := t.w.Close()
close(t.c)
return err
}
+524
View File
@@ -0,0 +1,524 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
import "testing"
func TestRecursiveTree(t *testing.T) {
n := NewRecursiveTreeTest(t, "testdata/vfs.txt")
defer n.Close()
ch := NewChans(5)
watches := [...]RCase{
// i=0
{
Call: Call{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/fs.go",
C: ch[0],
E: Create,
},
Record: []Call{
{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/fs.go",
E: Create,
},
},
},
// i=1
{
Call: Call{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/cmd/...",
C: ch[1],
E: Remove,
},
Record: []Call{
{
F: FuncRecursiveWatch,
P: "src/github.com/rjeczalik/fs/cmd",
E: Remove,
},
},
},
// i=2
{
Call: Call{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs",
C: ch[2],
E: Rename,
},
Record: []Call{
{
F: FuncRecursiveWatch,
P: "src/github.com/rjeczalik/fs",
E: Create | Remove | Rename,
},
{
F: FuncRecursiveUnwatch,
P: "src/github.com/rjeczalik/fs/cmd",
},
{
F: FuncUnwatch,
P: "src/github.com/rjeczalik/fs/fs.go",
},
},
},
// i=3
{
Call: Call{
F: FuncWatch,
P: "src/github.com/ppknap/link/README.md",
C: ch[0],
E: Create,
},
Record: []Call{
{
F: FuncWatch,
P: "src/github.com/ppknap/link/README.md",
E: Create,
},
},
},
// i=4
{
Call: Call{
F: FuncWatch,
P: "src/github.com/ppknap/link/include/...",
C: ch[3],
E: Remove,
},
Record: []Call{
{
F: FuncRecursiveWatch,
P: "src/github.com/ppknap/link/include",
E: Remove,
},
},
},
// i=5
{
Call: Call{
F: FuncWatch,
P: "src/github.com/ppknap/link/include",
C: ch[0],
E: Write,
},
Record: []Call{
{
F: FuncRecursiveRewatch,
P: "src/github.com/ppknap/link/include",
NP: "src/github.com/ppknap/link/include",
E: Remove,
NE: Remove | Write,
},
},
},
// i=6
{
Call: Call{
F: FuncWatch,
P: "src/github.com/ppknap/link/test/Jamfile.jam",
C: ch[0],
E: Rename,
},
Record: []Call{
{
F: FuncWatch,
P: "src/github.com/ppknap/link/test/Jamfile.jam",
E: Rename,
},
},
},
// i=7
{
Call: Call{
F: FuncWatch,
P: "src/github.com/ppknap/link/test/Jamfile.jam",
C: ch[0],
E: Create,
},
Record: []Call{
{
F: FuncRewatch,
P: "src/github.com/ppknap/link/test/Jamfile.jam",
E: Rename,
NE: Rename | Create,
},
},
},
// i=8
{
Call: Call{
F: FuncWatch,
P: "src/github.com/ppknap/...",
C: ch[0],
E: Create,
},
Record: []Call{
{
F: FuncRecursiveWatch,
P: "src/github.com/ppknap",
E: Create | Remove | Write | Rename,
},
{
F: FuncUnwatch,
P: "src/github.com/ppknap/link/README.md",
},
{
F: FuncRecursiveUnwatch,
P: "src/github.com/ppknap/link/include",
},
{
F: FuncUnwatch,
P: "src/github.com/ppknap/link/test/Jamfile.jam",
},
},
},
// i=9
{
Call: Call{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/README.md",
C: ch[0],
E: Rename,
},
Record: nil,
},
// i=10
{
Call: Call{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/cmd/gotree",
C: ch[2],
E: Create | Remove,
},
Record: nil,
},
// i=11
{
Call: Call{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu/src/main.cc",
C: ch[0],
E: Create,
},
Record: []Call{
{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu/src/main.cc",
E: Create,
},
},
},
// i=12
{
Call: Call{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu/src/main.cc",
C: ch[0],
E: Remove,
},
Record: []Call{
{
F: FuncRewatch,
P: "src/github.com/pblaszczyk/qttu/src/main.cc",
E: Create,
NE: Create | Remove,
},
},
},
// i=13
{
Call: Call{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu/src/main.cc",
C: ch[0],
E: Create | Remove,
},
Record: nil,
},
// i=14
{
Call: Call{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu/src",
C: ch[0],
E: Create,
},
Record: []Call{
{
F: FuncRecursiveRewatch,
P: "src/github.com/pblaszczyk/qttu/src/main.cc",
NP: "src/github.com/pblaszczyk/qttu/src",
E: Create | Remove,
NE: Create | Remove,
},
},
},
// i=15
{
Call: Call{
F: FuncWatch,
P: "src/github.com/pblaszczyk/qttu",
C: ch[4],
E: Write,
},
Record: []Call{
{
F: FuncRecursiveRewatch,
P: "src/github.com/pblaszczyk/qttu/src",
NP: "src/github.com/pblaszczyk/qttu",
E: Create | Remove,
NE: Create | Remove | Write,
},
},
},
// i=16
{
Call: Call{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/fs.go",
C: ch[3],
E: Rename,
},
Record: nil,
},
}
n.ExpectRecordedCalls(watches[:])
events := [...]TCase{
// i=0
{
Event: Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Rename},
Receiver: Chans{ch[2], ch[3]},
},
// i=1
{
Event: Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Create},
Receiver: Chans{ch[0]},
},
// i=2
{
Event: Call{P: "src/github.com/rjeczalik/fs/fs.go/file", E: Create},
Receiver: Chans{ch[0]},
},
// i=3
{
Event: Call{P: "src/github.com/rjeczalik/fs", E: Rename},
Receiver: Chans{ch[2]},
},
// i=4
{
Event: Call{P: "src/github.com/rjeczalik/fs/fs_test.go", E: Rename},
Receiver: Chans{ch[2]},
},
// i=5
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/mktree/main.go", E: Remove},
Receiver: Chans{ch[1]},
},
// i=6
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/gotree", E: Remove},
Receiver: Chans{ch[1], ch[2]},
},
// i=7
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd", E: Remove},
Receiver: Chans{ch[1]},
},
// i=8
{
Event: Call{P: "src/github.com/rjeczalik/fs/fs.go/file", E: Write},
Receiver: nil,
},
// i=9
{
Event: Call{P: "src/github.com/rjeczalik/fs/fs.go/file", E: Write},
Receiver: nil,
},
// i=10
{
Event: Call{P: "src/github.com/rjeczalik/fs", E: Remove},
Receiver: nil,
},
// i=11
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd", E: Rename},
Receiver: Chans{ch[2]},
},
// i=12
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/mktree/main.go", E: Write},
Receiver: nil,
},
// i=13
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/gotree", E: Rename},
Receiver: nil,
},
// i=14
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/file", E: Rename},
Receiver: nil,
},
// i=15
{
Event: Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Rename},
Receiver: Chans{ch[2], ch[3]},
},
}
n.ExpectTreeEvents(events[:], ch)
stops := [...]RCase{
// i=0
{
Call: Call{
F: FuncStop,
C: ch[1],
},
Record: nil,
},
{
Call: Call{
F: FuncStop,
C: ch[4],
},
Record: []Call{
{
F: FuncRecursiveRewatch,
P: "src/github.com/pblaszczyk/qttu",
NP: "src/github.com/pblaszczyk/qttu",
E: Create | Remove | Write,
NE: Create | Remove,
},
},
},
}
n.ExpectRecordedCalls(stops[:])
}
func TestRecursiveTreeWatchInactiveMerge(t *testing.T) {
n := NewRecursiveTreeTest(t, "testdata/vfs.txt")
defer n.Close()
ch := NewChans(1)
watches := [...]RCase{
// i=0
{
Call: Call{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs",
C: ch[0],
E: Create,
},
Record: []Call{
{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs",
E: Create,
},
},
},
// i=1
{
Call: Call{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs/cmd/gotree/...",
C: ch[0],
E: Remove,
},
Record: []Call{
{
F: FuncRecursiveRewatch,
P: "src/github.com/rjeczalik/fs",
NP: "src/github.com/rjeczalik/fs",
E: Create,
NE: Create | Remove,
},
},
},
}
n.ExpectRecordedCalls(watches[:])
events := [...]TCase{
// i=0
{
Event: Call{P: "src/github.com/rjeczalik/fs/.fs.go.swp", E: Create},
Receiver: Chans{ch[0]},
},
// i=1
{
Event: Call{P: "src/github.com/rjeczalik/fs/.fs.go.swp", E: Remove},
Receiver: nil,
},
// i=2
{
Event: Call{P: "src/github.com/rjeczalik/fs", E: Remove},
Receiver: nil,
},
// i=3
{
Event: Call{P: "src/github.com/rjeczalik/fs/cmd/gotree/main.go", E: Remove},
Receiver: Chans{ch[0]},
},
}
n.ExpectTreeEvents(events[:], ch)
}
func TestRecursiveTree_Windows(t *testing.T) {
n := NewRecursiveTreeTest(t, "testdata/vfs.txt")
defer n.Close()
const ChangeFileName = Event(0x1)
ch := NewChans(1)
watches := [...]RCase{
// i=0
{
Call: Call{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs",
C: ch[0],
E: ChangeFileName,
},
Record: []Call{
{
F: FuncWatch,
P: "src/github.com/rjeczalik/fs",
E: ChangeFileName,
},
},
},
}
n.ExpectRecordedCalls(watches[:])
events := [...]TCase{
// i=0
{
Event: Call{P: "src/github.com/rjeczalik/fs", E: ChangeFileName},
Receiver: Chans{ch[0]},
},
// i=1
{
Event: Call{P: "src/github.com/rjeczalik/fs/fs.go", E: ChangeFileName},
Receiver: Chans{ch[0]},
},
}
n.ExpectTreeEvents(events[:], ch)
}
+150
View File
@@ -0,0 +1,150 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
import (
"errors"
"os"
"path/filepath"
"strings"
)
const all = ^Event(0)
const sep = string(os.PathSeparator)
var errDepth = errors.New("exceeded allowed iteration count (circular symlink?)")
func min(i, j int) int {
if i > j {
return j
}
return i
}
func max(i, j int) int {
if i < j {
return j
}
return i
}
// must panics if err is non-nil.
func must(err error) {
if err != nil {
panic(err)
}
}
// nonil gives first non-nil error from the given arguments.
func nonil(err ...error) error {
for _, err := range err {
if err != nil {
return err
}
}
return nil
}
func cleanpath(path string) (realpath string, isrec bool, err error) {
if strings.HasSuffix(path, "...") {
isrec = true
path = path[:len(path)-3]
}
if path, err = filepath.Abs(path); err != nil {
return "", false, err
}
if path, err = canonical(path); err != nil {
return "", false, err
}
return path, isrec, nil
}
// canonical resolves any symlink in the given path and returns it in a clean form.
// It expects the path to be absolute. It fails to resolve circular symlinks by
// maintaining a simple iteration limit.
func canonical(p string) (string, error) {
p, err := filepath.Abs(p)
if err != nil {
return "", err
}
for i, j, depth := 1, 0, 1; i < len(p); i, depth = i+1, depth+1 {
if depth > 128 {
return "", &os.PathError{Op: "canonical", Path: p, Err: errDepth}
}
if j = strings.IndexRune(p[i:], '/'); j == -1 {
j, i = i, len(p)
} else {
j, i = i, i+j
}
fi, err := os.Lstat(p[:i])
if err != nil {
return "", err
}
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
s, err := os.Readlink(p[:i])
if err != nil {
return "", err
}
if filepath.IsAbs(s) {
p = "/" + s + p[i:]
} else {
p = p[:j] + s + p[i:]
}
i = 1 // no guarantee s is canonical, start all over
}
}
return filepath.Clean(p), nil
}
func joinevents(events []Event) (e Event) {
if len(events) == 0 {
e = All
} else {
for _, event := range events {
e |= event
}
}
return
}
func split(s string) (string, string) {
if i := lastIndexSep(s); i != -1 {
return s[:i], s[i+1:]
}
return "", s
}
func base(s string) string {
if i := lastIndexSep(s); i != -1 {
return s[i+1:]
}
return s
}
func indexbase(root, name string) int {
if n, m := len(root), len(name); m >= n && name[:n] == root &&
(n == m || name[n] == os.PathSeparator) {
return min(n+1, m)
}
return -1
}
func indexSep(s string) int {
for i := 0; i < len(s); i++ {
if s[i] == os.PathSeparator {
return i
}
}
return -1
}
func lastIndexSep(s string) int {
for i := len(s) - 1; i >= 0; i-- {
if s[i] == os.PathSeparator {
return i
}
}
return -1
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin
package notify
import (
"os"
"testing"
)
func TestCanonicalDarwin(t *testing.T) {
cases := [...]caseCanonical{
{"/etc", "/private/etc"},
{"/etc/defaults", "/private/etc/defaults"},
{"/etc/hosts", "/private/etc/hosts"},
{"/tmp", "/private/tmp"},
{"/var", "/private/var"},
}
testCanonical(t, cases[:])
}
func TestCanonicalDarwinMultiple(t *testing.T) {
etcsym, err := symlink("/etc", "")
if err != nil {
t.Fatal(err)
}
tmpsym, err := symlink("/tmp", "")
if err != nil {
t.Fatal(nonil(err, os.Remove(etcsym)))
}
defer removeall(etcsym, tmpsym)
cases := [...]caseCanonical{
{etcsym, "/private/etc"},
{etcsym + "/hosts", "/private/etc/hosts"},
{tmpsym, "/private/tmp"},
}
testCanonical(t, cases[:])
}
+142
View File
@@ -0,0 +1,142 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
import (
"path/filepath"
"testing"
)
type caseCanonical struct {
path string
full string
}
func testCanonical(t *testing.T, cases []caseCanonical) {
for i, cas := range cases {
full, err := canonical(cas.path)
if err != nil {
t.Errorf("want err=nil; got %v (i=%d)", err, i)
continue
}
if full != cas.full {
t.Errorf("want full=%q; got %q (i=%d)", cas.full, full, i)
continue
}
}
}
func TestCanonicalNoSymlink(t *testing.T) {
td := filepath.Join(wd, "testdata")
cases := [...]caseCanonical{
{".", wd},
{"testdata", td},
{filepath.Join("testdata", ".."), wd},
}
testCanonical(t, cases[:])
}
func TestJoinevents(t *testing.T) {
cases := [...]struct {
evs []Event
ev Event
}{
0: {nil, All},
1: {[]Event{}, All},
2: {[]Event{Create}, Create},
3: {[]Event{Rename}, Rename},
4: {[]Event{Create, Write, Remove}, Create | Write | Remove},
}
for i, cas := range cases {
if ev := joinevents(cas.evs); ev != cas.ev {
t.Errorf("want event=%v; got %v (i=%d)", cas.ev, ev, i)
}
}
}
func TestTreeSplit(t *testing.T) {
cases := [...]struct {
path string
dir string
base string
}{
{"/github.com/rjeczalik/fakerpc", "/github.com/rjeczalik", "fakerpc"},
{"/home/rjeczalik/src", "/home/rjeczalik", "src"},
{"/Users/pknap/porn/gopher.avi", "/Users/pknap/porn", "gopher.avi"},
{"C:/Documents and Users", "C:", "Documents and Users"},
{"C:/Documents and Users/pblaszczyk/wiertarka.exe", "C:/Documents and Users/pblaszczyk", "wiertarka.exe"},
{"/home/(╯°□°)╯︵ ┻━┻", "/home", "(╯°□°)╯︵ ┻━┻"},
}
for i, cas := range cases {
dir, base := split(filepath.FromSlash(cas.path))
if want := filepath.FromSlash(cas.dir); dir != want {
t.Errorf("want dir=%s; got %s (i=%d)", want, dir, i)
}
if want := filepath.FromSlash(cas.base); base != want {
t.Errorf("want base=%s; got %s (i=%d)", want, base, i)
}
}
}
func TestTreeBase(t *testing.T) {
cases := [...]struct {
path string
base string
}{
{"/github.com/rjeczalik/fakerpc", "fakerpc"},
{"/home/rjeczalik/src", "src"},
{"/Users/pknap/porn/gopher.avi", "gopher.avi"},
{"C:/Documents and Users", "Documents and Users"},
{"C:/Documents and Users/pblaszczyk/wiertarka.exe", "wiertarka.exe"},
{"/home/(╯°□°)╯︵ ┻━┻", "(╯°□°)╯︵ ┻━┻"},
}
for i, cas := range cases {
if base := base(filepath.FromSlash(cas.path)); base != cas.base {
t.Errorf("want base=%s; got %s (i=%d)", cas.base, base, i)
}
}
}
func TestTreeIndexSep(t *testing.T) {
cases := [...]struct {
path string
n int
}{
{"github.com/rjeczalik/fakerpc", 10},
{"home/rjeczalik/src", 4},
{"Users/pknap/porn/gopher.avi", 5},
{"C:/Documents and Users", 2},
{"Documents and Users/pblaszczyk/wiertarka.exe", 19},
{"(╯°□°)╯︵ ┻━┻/Downloads", 30},
}
for i, cas := range cases {
if n := indexSep(filepath.FromSlash(cas.path)); n != cas.n {
t.Errorf("want n=%d; got %d (i=%d)", cas.n, n, i)
}
}
}
func TestTreeLastIndexSep(t *testing.T) {
cases := [...]struct {
path string
n int
}{
{"github.com/rjeczalik/fakerpc", 20},
{"home/rjeczalik/src", 14},
{"Users/pknap/porn/gopher.avi", 16},
{"C:/Documents and Users", 2},
{"Documents and Users/pblaszczyk/wiertarka.exe", 30},
{"/home/(╯°□°)╯︵ ┻━┻", 5},
}
for i, cas := range cases {
if n := lastIndexSep(filepath.FromSlash(cas.path)); n != cas.n {
t.Errorf("want n=%d; got %d (i=%d)", cas.n, n, i)
}
}
}
func TestCleanpath(t *testing.T) {
t.Skip("TODO(rjeczalik)")
}
+125
View File
@@ -0,0 +1,125 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build !windows
package notify
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func tmpfile(s string) (string, error) {
f, err := ioutil.TempFile(filepath.Split(s))
if err != nil {
return "", err
}
if err = nonil(f.Sync(), f.Close()); err != nil {
return "", err
}
return f.Name(), nil
}
func symlink(src, dst string) (string, error) {
name, err := tmpfile(dst)
if err != nil {
return "", err
}
if err = nonil(os.Remove(name), os.Symlink(src, name)); err != nil {
return "", err
}
return name, nil
}
func removeall(s ...string) {
for _, s := range s {
os.Remove(s)
}
}
func TestCanonical(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatalf("os.Getwd()=%v", err)
}
wdsym, err := symlink(wd, "")
if err != nil {
t.Fatalf(`symlink(%q, "")=%v`, wd, err)
}
td := filepath.Join(wd, "testdata")
tdsym, err := symlink(td, td)
if err != nil {
t.Errorf("symlink(%q, %q)=%v", td, td, nonil(err, os.Remove(wdsym)))
}
defer removeall(wdsym, tdsym)
vfstxt := filepath.Join(td, "vfs.txt")
cases := [...]caseCanonical{
{wdsym, wd},
{tdsym, td},
{filepath.Join(wdsym, "notify.go"), filepath.Join(wd, "notify.go")},
{filepath.Join(tdsym, "vfs.txt"), vfstxt},
{filepath.Join(wdsym, filepath.Base(tdsym), "vfs.txt"), vfstxt},
}
testCanonical(t, cases[:])
}
func TestCanonicalCircular(t *testing.T) {
tmp1, err := tmpfile("circular")
if err != nil {
t.Fatal(err)
}
tmp2, err := tmpfile("circular")
if err != nil {
t.Fatal(nonil(err, os.Remove(tmp1)))
}
defer removeall(tmp1, tmp2)
// Symlink tmp1 -> tmp2.
if err = nonil(os.Remove(tmp1), os.Symlink(tmp2, tmp1)); err != nil {
t.Fatal(err)
}
// Symlnik tmp2 -> tmp1.
if err = nonil(os.Remove(tmp2), os.Symlink(tmp1, tmp2)); err != nil {
t.Fatal(err)
}
if _, err = canonical(tmp1); err == nil {
t.Fatalf("want canonical(%q)!=nil", tmp1)
}
if _, ok := err.(*os.PathError); !ok {
t.Fatalf("want canonical(%q)=os.PathError; got %T", tmp1, err)
}
}
// issue #83
func TestCanonical_RelativeSymlink(t *testing.T) {
dir, err := ioutil.TempDir(wd, "")
if err != nil {
t.Fatalf("TempDir()=%v", err)
}
var (
path = filepath.Join(dir, filepath.FromSlash("a/b/c/d/e/f"))
realpath = filepath.Join(dir, filepath.FromSlash("a/b/x/y/z/d/e/f"))
rel = filepath.FromSlash("x/y/z/../z/../z")
chdir = filepath.Join(dir, filepath.FromSlash("a/b"))
)
defer os.RemoveAll(dir)
if err = os.MkdirAll(realpath, 0755); err != nil {
t.Fatalf("MkdirAll()=%v", err)
}
if err := os.Chdir(chdir); err != nil {
t.Fatalf("Chdir()=%v", err)
}
if err := nonil(os.Symlink(rel, "c"), os.Chdir(wd)); err != nil {
t.Fatalf("Symlink()=%v", err)
}
got, err := canonical(path)
if err != nil {
t.Fatalf("canonical(%s)=%v", path, err)
}
if got != realpath {
t.Fatalf("want canonical()=%s; got %s", realpath, got)
}
}
+85
View File
@@ -0,0 +1,85 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
import "errors"
var (
errAlreadyWatched = errors.New("path is already watched")
errNotWatched = errors.New("path is not being watched")
errInvalidEventSet = errors.New("invalid event set provided")
)
// Watcher is a intermediate interface for wrapping inotify, ReadDirChangesW,
// FSEvents, kqueue and poller implementations.
//
// The watcher implementation is expected to do its own mapping between paths and
// create watchers if underlying event notification does not support it. For
// the ease of implementation it is guaranteed that paths provided via Watch and
// Unwatch methods are absolute and clean.
type watcher interface {
// Watch requests a watcher creation for the given path and given event set.
Watch(path string, event Event) error
// Unwatch requests a watcher deletion for the given path and given event set.
Unwatch(path string) error
// Rewatch provides a functionality for modifying existing watch-points, like
// expanding its event set.
//
// Rewatch modifies existing watch-point under for the given path. It passes
// the existing event set currently registered for the given path, and the
// new, requested event set.
//
// It is guaranteed that Tree will not pass to Rewatch zero value for any
// of its arguments. If old == new and watcher can be upgraded to
// recursiveWatcher interface, a watch for the corresponding path is expected
// to be changed from recursive to the non-recursive one.
Rewatch(path string, old, new Event) error
// Close unwatches all paths that are registered. When Close returns, it
// is expected it will report no more events.
Close() error
}
// RecursiveWatcher is an interface for a Watcher for those OS, which do support
// recursive watching over directories.
type recursiveWatcher interface {
RecursiveWatch(path string, event Event) error
// RecursiveUnwatch removes a recursive watch-point given by the path. For
// native recursive implementation there is no difference in functionality
// between Unwatch and RecursiveUnwatch, however for those platforms, that
// requires emulation for recursive watch-points, the implementation differs.
RecursiveUnwatch(path string) error
// RecursiveRewatcher provides a functionality for modifying and/or relocating
// existing recursive watch-points.
//
// To relocate a watch-point means to unwatch oldpath and set a watch-point on
// newpath.
//
// To modify a watch-point means either to expand or shrink its event set.
//
// Tree can want to either relocate, modify or relocate and modify a watch-point
// via single RecursiveRewatch call.
//
// If oldpath == newpath, the watch-point is expected to change its event set value
// from oldevent to newevent.
//
// If oldevent == newevent, the watch-point is expected to relocate from oldpath
// to the newpath.
//
// If oldpath != newpath and oldevent != newevent, the watch-point is expected
// to relocate from oldpath to the newpath first and then change its event set
// value from oldevent to the newevent. In other words the end result must be
// a watch-point set on newpath with newevent value of its event set.
//
// It is guaranteed that Tree will not pass to RecurisveRewatcha zero value
// for any of its arguments. If oldpath == newpath and oldevent == newevent,
// a watch for the corresponding path is expected to be changed for
// non-recursive to the recursive one.
RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error
}
+161
View File
@@ -0,0 +1,161 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build solaris
package notify
import (
"fmt"
"os"
"syscall"
)
// newTrigger returns implementation of trigger.
func newTrigger(pthLkp map[string]*watched) trigger {
return &fen{
pthLkp: pthLkp,
cf: newCfen(),
}
}
// fen is a structure implementing trigger for FEN.
type fen struct {
// p is a FEN port identifier
p int
// pthLkp is a structure mapping monitored files/dir with data about them,
// shared with parent trg structure
pthLkp map[string]*watched
// cf wraps C operations for FEN
cf cfen
}
// watched is a data structure representing watched file/directory.
type watched struct {
trgWatched
}
// Stop implements trigger.
func (f *fen) Stop() error {
return f.cf.portAlert(f.p)
}
// Close implements trigger.
func (f *fen) Close() (err error) {
return syscall.Close(f.p)
}
// NewWatched implements trigger.
func (*fen) NewWatched(p string, fi os.FileInfo) (*watched, error) {
return &watched{trgWatched{p: p, fi: fi}}, nil
}
// Record implements trigger.
func (f *fen) Record(w *watched) {
f.pthLkp[w.p] = w
}
// Del implements trigger.
func (f *fen) Del(w *watched) {
delete(f.pthLkp, w.p)
}
func inter2pe(n interface{}) PortEvent {
pe, ok := n.(PortEvent)
if !ok {
panic(fmt.Sprintf("fen: type should be PortEvent, %T instead", n))
}
return pe
}
// Watched implements trigger.
func (f *fen) Watched(n interface{}) (*watched, int64, error) {
pe := inter2pe(n)
fo, ok := pe.PortevObject.(*FileObj)
if !ok || fo == nil {
panic(fmt.Sprintf("fen: type should be *FileObj, %T instead", fo))
}
w, ok := f.pthLkp[fo.Name]
if !ok {
return nil, 0, errNotWatched
}
return w, int64(pe.PortevEvents), nil
}
// init initializes FEN.
func (f *fen) Init() (err error) {
f.p, err = f.cf.portCreate()
return
}
func fi2fo(fi os.FileInfo, p string) FileObj {
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
panic(fmt.Sprintf("fen: type should be *syscall.Stat_t, %T instead", st))
}
return FileObj{Name: p, Atim: st.Atim, Mtim: st.Mtim, Ctim: st.Ctim}
}
// Unwatch implements trigger.
func (f *fen) Unwatch(w *watched) error {
return f.cf.portDissociate(f.p, FileObj{Name: w.p})
}
// Watch implements trigger.
func (f *fen) Watch(fi os.FileInfo, w *watched, e int64) error {
return f.cf.portAssociate(f.p, fi2fo(fi, w.p), int(e))
}
// Wait implements trigger.
func (f *fen) Wait() (interface{}, error) {
var (
pe PortEvent
err error
)
err = f.cf.portGet(f.p, &pe)
return pe, err
}
// IsStop implements trigger.
func (f *fen) IsStop(n interface{}, err error) bool {
return err == syscall.EBADF || inter2pe(n).PortevSource == srcAlert
}
func init() {
encode = func(e Event, dir bool) (o int64) {
// Create event is not supported by FEN. Instead FileModified event will
// be registered. If this event will be reported on dir which is to be
// monitored for Create, dir will be rescanned and Create events will
// be generated and returned for new files. In case of files,
// if not requested FileModified event is reported, it will be ignored.
o = int64(e &^ Create)
if (e&Create != 0 && dir) || e&Write != 0 {
o = (o &^ int64(Write)) | int64(FileModified)
}
// Following events are 'exception events' and as such cannot be requested
// explicitly for monitoring or filtered out. If the will be reported
// by FEN and not subscribed with by user, they will be filtered out by
// watcher's logic.
o &= int64(^Rename & ^Remove &^ FileDelete &^ FileRenameTo &^
FileRenameFrom &^ Unmounted &^ MountedOver)
return
}
nat2not = map[Event]Event{
FileModified: Write,
FileRenameFrom: Rename,
FileDelete: Remove,
FileAccess: Event(0),
FileAttrib: Event(0),
FileRenameTo: Event(0),
FileTrunc: Event(0),
FileNoFollow: Event(0),
Unmounted: Event(0),
MountedOver: Event(0),
}
not2nat = map[Event]Event{
Write: FileModified,
Rename: FileRenameFrom,
Remove: FileDelete,
}
}
+141
View File
@@ -0,0 +1,141 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build solaris
package notify
// #include <port.h>
// #include <stdio.h>
// #include <stdlib.h>
// struct file_obj* newFo() { return (struct file_obj*) malloc(sizeof(struct file_obj)); }
// port_event_t* newPe() { return (port_event_t*) malloc(sizeof(port_event_t)); }
// uintptr_t conv(struct file_obj* fo) { return (uintptr_t) fo; }
// struct file_obj* dconv(uintptr_t fo) { return (struct file_obj*) fo; }
import "C"
import (
"syscall"
"unsafe"
)
const (
fileAccess = Event(C.FILE_ACCESS)
fileModified = Event(C.FILE_MODIFIED)
fileAttrib = Event(C.FILE_ATTRIB)
fileDelete = Event(C.FILE_DELETE)
fileRenameTo = Event(C.FILE_RENAME_TO)
fileRenameFrom = Event(C.FILE_RENAME_FROM)
fileTrunc = Event(C.FILE_TRUNC)
fileNoFollow = Event(C.FILE_NOFOLLOW)
unmounted = Event(C.UNMOUNTED)
mountedOver = Event(C.MOUNTEDOVER)
)
// PortEvent is a notify's equivalent of port_event_t.
type PortEvent struct {
PortevEvents int // PortevEvents is an equivalent of portev_events.
PortevSource uint8 // PortevSource is an equivalent of portev_source.
PortevPad uint8 // Portevpad is an equivalent of portev_pad.
PortevObject interface{} // PortevObject is an equivalent of portev_object.
PortevUser uintptr // PortevUser is an equivalent of portev_user.
}
// FileObj is a notify's equivalent of file_obj.
type FileObj struct {
Atim syscall.Timespec // Atim is an equivalent of fo_atime.
Mtim syscall.Timespec // Mtim is an equivalent of fo_mtime.
Ctim syscall.Timespec // Ctim is an equivalent of fo_ctime.
Pad [3]uintptr // Pad is an equivalent of fo_pad.
Name string // Name is an equivalent of fo_name.
}
type cfen struct {
p2pe map[string]*C.port_event_t
p2fo map[string]*C.struct_file_obj
}
func newCfen() cfen {
return cfen{
p2pe: make(map[string]*C.port_event_t),
p2fo: make(map[string]*C.struct_file_obj),
}
}
func unix2C(sec int64, nsec int64) (C.time_t, C.long) {
return C.time_t(sec), C.long(nsec)
}
func (c *cfen) portAssociate(p int, fo FileObj, e int) (err error) {
cfo := C.newFo()
cfo.fo_atime.tv_sec, cfo.fo_atime.tv_nsec = unix2C(fo.Atim.Unix())
cfo.fo_mtime.tv_sec, cfo.fo_mtime.tv_nsec = unix2C(fo.Mtim.Unix())
cfo.fo_ctime.tv_sec, cfo.fo_ctime.tv_nsec = unix2C(fo.Ctim.Unix())
cfo.fo_name = C.CString(fo.Name)
c.p2fo[fo.Name] = cfo
_, err = C.port_associate(C.int(p), srcFile, C.conv(cfo), C.int(e), nil)
return
}
func (c *cfen) portDissociate(port int, fo FileObj) (err error) {
cfo, ok := c.p2fo[fo.Name]
if !ok {
return errNotWatched
}
_, err = C.port_dissociate(C.int(port), srcFile, C.conv(cfo))
C.free(unsafe.Pointer(cfo.fo_name))
C.free(unsafe.Pointer(cfo))
delete(c.p2fo, fo.Name)
return
}
const srcAlert = C.PORT_SOURCE_ALERT
const srcFile = C.PORT_SOURCE_FILE
const alertSet = C.PORT_ALERT_SET
func cfo2fo(cfo *C.struct_file_obj) *FileObj {
// Currently remaining attributes are not used.
if cfo == nil {
return nil
}
var fo FileObj
fo.Name = C.GoString(cfo.fo_name)
return &fo
}
func (c *cfen) portGet(port int, pe *PortEvent) (err error) {
cpe := C.newPe()
if _, err = C.port_get(C.int(port), cpe, nil); err != nil {
C.free(unsafe.Pointer(cpe))
return
}
pe.PortevEvents, pe.PortevSource, pe.PortevPad =
int(cpe.portev_events), uint8(cpe.portev_source), uint8(cpe.portev_pad)
pe.PortevObject = cfo2fo(C.dconv(cpe.portev_object))
pe.PortevUser = uintptr(cpe.portev_user)
C.free(unsafe.Pointer(cpe))
return
}
func (c *cfen) portCreate() (int, error) {
p, err := C.port_create()
return int(p), err
}
func (c *cfen) portAlert(p int) (err error) {
_, err = C.port_alert(C.int(p), alertSet, C.int(666), nil)
return
}
func (c *cfen) free() {
for i := range c.p2fo {
C.free(unsafe.Pointer(c.p2fo[i].fo_name))
C.free(unsafe.Pointer(c.p2fo[i]))
}
for i := range c.p2pe {
C.free(unsafe.Pointer(c.p2pe[i]))
}
c.p2fo = make(map[string]*C.struct_file_obj)
c.p2pe = make(map[string]*C.port_event_t)
}
+92
View File
@@ -0,0 +1,92 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build solaris
package notify
import (
"os"
"path/filepath"
"testing"
)
func fremove(w *W, path string, files []string) WCase {
cas := remove(w, path)
cas.Events[0] = &Call{P: path, E: FileDelete}
for _, f := range files {
cas.Events = append(cas.Events, &Call{P: f, E: FileDelete})
}
return cas
}
func fwrite(w *W, path string, p []byte) WCase {
cas := write(w, path, p)
path = cas.Events[0].Path()
cas.Events[0] = &Call{P: path, E: FileModified}
return cas
}
func frename(w *W, path string, files []string) WCase {
const ext = ".notify"
cas := WCase{
Action: func() {
file := filepath.Join(w.root, path)
if err := os.Rename(file, file+ext); err != nil {
w.Fatalf("Rename(%q, %q)=%v", path, path+ext, err)
}
},
Events: []EventInfo{
&Call{P: path + ext, E: osSpecificCreate},
&Call{P: path, E: FileRenameFrom},
},
}
for _, f := range files {
cas.Events = append(cas.Events, &Call{P: f, E: FileRenameFrom})
}
return cas
}
var events = []Event{
FileModified,
FileAttrib,
FileRenameFrom,
osSpecificCreate,
FileDelete,
}
func TestWatcherFen(t *testing.T) {
w := NewWatcherTest(t, "testdata/vfs.txt", events...)
defer w.Close()
cases := [...]WCase{
fremove(w, "src/github.com/ppknap/link/include/coost/link", []string{
"src/github.com/ppknap/link/include/coost/link/definitions.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/bundle.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/container_invoker.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/container_value_trait.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/dummy_type.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/function_trait.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/immediate_invoker.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/stdhelpers/always_same.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/stdhelpers/make_unique.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/stdhelpers",
"src/github.com/ppknap/link/include/coost/link/detail/vertex.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/wire.hpp",
"src/github.com/ppknap/link/include/coost/link/detail",
"src/github.com/ppknap/link/include/coost/link/link.hpp",
},
),
fwrite(w, "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
fremove(w, "src/github.com/ppknap/link/README.md", nil),
frename(w, "src/github.com/rjeczalik/fs/fs.go", nil),
frename(w, "src/github.com/rjeczalik/fs/cmd/gotree", []string{
"src/github.com/rjeczalik/fs/cmd/gotree/go.go",
"src/github.com/rjeczalik/fs/cmd/gotree/main.go",
},
),
}
w.ExpectAll(cases[:])
}
+311
View File
@@ -0,0 +1,311 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin,!kqueue
package notify
import (
"errors"
"strings"
"sync/atomic"
)
const (
failure = uint32(FSEventsMustScanSubDirs | FSEventsUserDropped | FSEventsKernelDropped)
filter = uint32(FSEventsCreated | FSEventsRemoved | FSEventsRenamed |
FSEventsModified | FSEventsInodeMetaMod)
)
// FSEvent represents single file event. It is created out of values passed by
// FSEvents to FSEventStreamCallback function.
type FSEvent struct {
Path string // real path of the file or directory
ID uint64 // ID of the event (FSEventStreamEventId)
Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags)
}
// splitflags separates event flags from single set into slice of flags.
func splitflags(set uint32) (e []uint32) {
for i := uint32(1); set != 0; i, set = i<<1, set>>1 {
if (set & 1) != 0 {
e = append(e, i)
}
}
return
}
// watch represents a filesystem watchpoint. It is a higher level abstraction
// over FSEvents' stream, which implements filtering of file events based
// on path and event set. It emulates non-recursive watch-point by filtering out
// events which paths are more than 1 level deeper than the watched path.
type watch struct {
// prev stores last event set per path in order to filter out old flags
// for new events, which appratenly FSEvents likes to retain. It's a disgusting
// hack, it should be researched how to get rid of it.
prev map[string]uint32
c chan<- EventInfo
stream *stream
path string
events uint32
isrec int32
flushed bool
}
// Example format:
//
// ~ $ (trigger command) # (event set) -> (effective event set)
//
// Heuristics:
//
// 1. Create event is removed when it was present in previous event set.
// Example:
//
// ~ $ echo > file # Create|Write -> Create|Write
// ~ $ echo > file # Create|Write|InodeMetaMod -> Write|InodeMetaMod
//
// 2. Remove event is removed if it was present in previouse event set.
// Example:
//
// ~ $ touch file # Create -> Create
// ~ $ rm file # Create|Remove -> Remove
// ~ $ touch file # Create|Remove -> Create
//
// 3. Write event is removed if not followed by InodeMetaMod on existing
// file. Example:
//
// ~ $ echo > file # Create|Write -> Create|Write
// ~ $ chmod +x file # Create|Write|ChangeOwner -> ChangeOwner
//
// 4. Write&InodeMetaMod is removed when effective event set contain Remove event.
// Example:
//
// ~ $ echo > file # Write|InodeMetaMod -> Write|InodeMetaMod
// ~ $ rm file # Remove|Write|InodeMetaMod -> Remove
//
func (w *watch) strip(base string, set uint32) uint32 {
const (
write = FSEventsModified | FSEventsInodeMetaMod
both = FSEventsCreated | FSEventsRemoved
)
switch w.prev[base] {
case FSEventsCreated:
set &^= FSEventsCreated
if set&FSEventsRemoved != 0 {
w.prev[base] = FSEventsRemoved
set &^= write
}
case FSEventsRemoved:
set &^= FSEventsRemoved
if set&FSEventsCreated != 0 {
w.prev[base] = FSEventsCreated
}
default:
switch set & both {
case FSEventsCreated:
w.prev[base] = FSEventsCreated
case FSEventsRemoved:
w.prev[base] = FSEventsRemoved
set &^= write
}
}
dbgprintf("split()=%v\n", Event(set))
return set
}
// Dispatch is a stream function which forwards given file events for the watched
// path to underlying FileInfo channel.
func (w *watch) Dispatch(ev []FSEvent) {
events := atomic.LoadUint32(&w.events)
isrec := (atomic.LoadInt32(&w.isrec) == 1)
for i := range ev {
if ev[i].Flags&FSEventsHistoryDone != 0 {
w.flushed = true
continue
}
if !w.flushed {
continue
}
dbgprintf("%v (0x%x) (%s, i=%d, ID=%d, len=%d)\n", Event(ev[i].Flags),
ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev))
if ev[i].Flags&failure != 0 {
// TODO(rjeczalik): missing error handling
continue
}
if !strings.HasPrefix(ev[i].Path, w.path) {
continue
}
n := len(w.path)
base := ""
if len(ev[i].Path) > n {
if ev[i].Path[n] != '/' {
continue
}
base = ev[i].Path[n+1:]
if !isrec && strings.IndexByte(base, '/') != -1 {
continue
}
}
// TODO(rjeczalik): get diff only from filtered events?
e := w.strip(string(base), ev[i].Flags) & events
if e == 0 {
continue
}
for _, e := range splitflags(e) {
dbgprintf("%d: single event: %v", ev[i].ID, Event(e))
w.c <- &event{
fse: ev[i],
event: Event(e),
}
}
}
}
// Stop closes underlying FSEvents stream and stops dispatching events.
func (w *watch) Stop() {
w.stream.Stop()
// TODO(rjeczalik): make (*stream).Stop flush synchronously undelivered events,
// so the following hack can be removed. It should flush all the streams
// concurrently as we care not to block too much here.
atomic.StoreUint32(&w.events, 0)
atomic.StoreInt32(&w.isrec, 0)
}
// fsevents implements Watcher and RecursiveWatcher interfaces backed by FSEvents
// framework.
type fsevents struct {
watches map[string]*watch
c chan<- EventInfo
}
func newWatcher(c chan<- EventInfo) watcher {
return &fsevents{
watches: make(map[string]*watch),
c: c,
}
}
func (fse *fsevents) watch(path string, event Event, isrec int32) (err error) {
if _, ok := fse.watches[path]; ok {
return errAlreadyWatched
}
w := &watch{
prev: make(map[string]uint32),
c: fse.c,
path: path,
events: uint32(event),
isrec: isrec,
}
w.stream = newStream(path, w.Dispatch)
if err = w.stream.Start(); err != nil {
return err
}
fse.watches[path] = w
return nil
}
func (fse *fsevents) unwatch(path string) (err error) {
w, ok := fse.watches[path]
if !ok {
return errNotWatched
}
w.stream.Stop()
delete(fse.watches, path)
return nil
}
// Watch implements Watcher interface. It fails with non-nil error when setting
// the watch-point by FSEvents fails or with errAlreadyWatched error when
// the given path is already watched.
func (fse *fsevents) Watch(path string, event Event) error {
return fse.watch(path, event, 0)
}
// Unwatch implements Watcher interface. It fails with errNotWatched when
// the given path is not being watched.
func (fse *fsevents) Unwatch(path string) error {
return fse.unwatch(path)
}
// Rewatch implements Watcher interface. It fails with errNotWatched when
// the given path is not being watched or with errInvalidEventSet when oldevent
// does not match event set the watch-point currently holds.
func (fse *fsevents) Rewatch(path string, oldevent, newevent Event) error {
w, ok := fse.watches[path]
if !ok {
return errNotWatched
}
if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
return errInvalidEventSet
}
atomic.StoreInt32(&w.isrec, 0)
return nil
}
// RecursiveWatch implements RecursiveWatcher interface. It fails with non-nil
// error when setting the watch-point by FSEvents fails or with errAlreadyWatched
// error when the given path is already watched.
func (fse *fsevents) RecursiveWatch(path string, event Event) error {
return fse.watch(path, event, 1)
}
// RecursiveUnwatch implements RecursiveWatcher interface. It fails with
// errNotWatched when the given path is not being watched.
//
// TODO(rjeczalik): fail if w.isrec == 0?
func (fse *fsevents) RecursiveUnwatch(path string) error {
return fse.unwatch(path)
}
// RecrusiveRewatch implements RecursiveWatcher interface. It fails:
//
// * with errNotWatched when the given path is not being watched
// * with errInvalidEventSet when oldevent does not match the current event set
// * with errAlreadyWatched when watch-point given by the oldpath was meant to
// be relocated to newpath, but the newpath is already watched
// * a non-nil error when setting the watch-point with FSEvents fails
//
// TODO(rjeczalik): Improve handling of watch-point relocation? See two TODOs
// that follows.
func (fse *fsevents) RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error {
switch [2]bool{oldpath == newpath, oldevent == newevent} {
case [2]bool{true, true}:
w, ok := fse.watches[oldpath]
if !ok {
return errNotWatched
}
atomic.StoreInt32(&w.isrec, 1)
return nil
case [2]bool{true, false}:
w, ok := fse.watches[oldpath]
if !ok {
return errNotWatched
}
if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
return errors.New("invalid event state diff")
}
atomic.StoreInt32(&w.isrec, 1)
return nil
default:
// TODO(rjeczalik): rewatch newpath only if exists?
// TODO(rjeczalik): migrate w.prev to new watch?
if _, ok := fse.watches[newpath]; ok {
return errAlreadyWatched
}
if err := fse.Unwatch(oldpath); err != nil {
return err
}
// TODO(rjeczalik): revert unwatch if watch fails?
return fse.watch(newpath, newevent, 1)
}
}
// Close unwatches all watch-points.
func (fse *fsevents) Close() error {
for _, w := range fse.watches {
w.Stop()
}
fse.watches = nil
return nil
}
+197
View File
@@ -0,0 +1,197 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin,!kqueue
package notify
/*
#include <CoreServices/CoreServices.h>
typedef void (*CFRunLoopPerformCallBack)(void*);
void gosource(void *);
void gostream(uintptr_t, uintptr_t, size_t, uintptr_t, uintptr_t, uintptr_t);
static FSEventStreamRef EventStreamCreate(FSEventStreamContext * context, uintptr_t info, CFArrayRef paths, FSEventStreamEventId since, CFTimeInterval latency, FSEventStreamCreateFlags flags) {
context->info = (void*) info;
return FSEventStreamCreate(NULL, (FSEventStreamCallback) gostream, context, paths, since, latency, flags);
}
#cgo LDFLAGS: -framework CoreServices
*/
import "C"
import (
"errors"
"os"
"runtime"
"sync"
"sync/atomic"
"unsafe"
)
var nilstream C.FSEventStreamRef
// Default arguments for FSEventStreamCreate function.
var (
latency C.CFTimeInterval
flags = C.FSEventStreamCreateFlags(C.kFSEventStreamCreateFlagFileEvents | C.kFSEventStreamCreateFlagNoDefer)
since = uint64(C.FSEventsGetCurrentEventId())
)
var runloop C.CFRunLoopRef // global runloop which all streams are registered with
var wg sync.WaitGroup // used to wait until the runloop starts
// source is used for synchronization purposes - it signals when runloop has
// started and is ready via the wg. It also serves purpose of a dummy source,
// thanks to it the runloop does not return as it also has at least one source
// registered.
var source = C.CFRunLoopSourceCreate(nil, 0, &C.CFRunLoopSourceContext{
perform: (C.CFRunLoopPerformCallBack)(C.gosource),
})
// Errors returned when FSEvents functions fail.
var (
errCreate = os.NewSyscallError("FSEventStreamCreate", errors.New("NULL"))
errStart = os.NewSyscallError("FSEventStreamStart", errors.New("false"))
)
// initializes the global runloop and ensures any created stream awaits its
// readiness.
func init() {
wg.Add(1)
go func() {
// There is exactly one run loop per thread. Lock this goroutine to its
// thread to ensure that it's not rescheduled on a different thread while
// setting up the run loop.
runtime.LockOSThread()
runloop = C.CFRunLoopGetCurrent()
C.CFRunLoopAddSource(runloop, source, C.kCFRunLoopDefaultMode)
C.CFRunLoopRun()
panic("runloop has just unexpectedly stopped")
}()
C.CFRunLoopSourceSignal(source)
}
//export gosource
func gosource(unsafe.Pointer) {
wg.Done()
}
//export gostream
func gostream(_, info uintptr, n C.size_t, paths, flags, ids uintptr) {
const (
offchar = unsafe.Sizeof((*C.char)(nil))
offflag = unsafe.Sizeof(C.FSEventStreamEventFlags(0))
offid = unsafe.Sizeof(C.FSEventStreamEventId(0))
)
if n == 0 {
return
}
fn := streamFuncs.get(info)
if fn == nil {
return
}
ev := make([]FSEvent, 0, int(n))
for i := uintptr(0); i < uintptr(n); i++ {
switch flags := *(*uint32)(unsafe.Pointer((flags + i*offflag))); {
case flags&uint32(FSEventsEventIdsWrapped) != 0:
atomic.StoreUint64(&since, uint64(C.FSEventsGetCurrentEventId()))
default:
ev = append(ev, FSEvent{
Path: C.GoString(*(**C.char)(unsafe.Pointer(paths + i*offchar))),
Flags: flags,
ID: *(*uint64)(unsafe.Pointer(ids + i*offid)),
})
}
}
fn(ev)
}
// StreamFunc is a callback called when stream receives file events.
type streamFunc func([]FSEvent)
var streamFuncs = streamFuncRegistry{m: map[uintptr]streamFunc{}}
type streamFuncRegistry struct {
mu sync.Mutex
m map[uintptr]streamFunc
i uintptr
}
func (r *streamFuncRegistry) get(id uintptr) streamFunc {
r.mu.Lock()
defer r.mu.Unlock()
return r.m[id]
}
func (r *streamFuncRegistry) add(fn streamFunc) uintptr {
r.mu.Lock()
defer r.mu.Unlock()
r.i++
r.m[r.i] = fn
return r.i
}
func (r *streamFuncRegistry) delete(id uintptr) {
r.mu.Lock()
defer r.mu.Unlock()
delete(r.m, id)
}
// Stream represents single watch-point which listens for events scheduled by
// the global runloop.
type stream struct {
path string
ref C.FSEventStreamRef
info uintptr
}
// NewStream creates a stream for given path, listening for file events and
// calling fn upon receiving any.
func newStream(path string, fn streamFunc) *stream {
return &stream{
path: path,
info: streamFuncs.add(fn),
}
}
// Start creates a FSEventStream for the given path and schedules it with
// global runloop. It's a nop if the stream was already started.
func (s *stream) Start() error {
if s.ref != nilstream {
return nil
}
wg.Wait()
p := C.CFStringCreateWithCStringNoCopy(nil, C.CString(s.path), C.kCFStringEncodingUTF8, nil)
path := C.CFArrayCreate(nil, (*unsafe.Pointer)(unsafe.Pointer(&p)), 1, nil)
ctx := C.FSEventStreamContext{}
ref := C.EventStreamCreate(&ctx, C.uintptr_t(s.info), path, C.FSEventStreamEventId(atomic.LoadUint64(&since)), latency, flags)
if ref == nilstream {
return errCreate
}
C.FSEventStreamScheduleWithRunLoop(ref, runloop, C.kCFRunLoopDefaultMode)
if C.FSEventStreamStart(ref) == C.Boolean(0) {
C.FSEventStreamInvalidate(ref)
return errStart
}
C.CFRunLoopWakeUp(runloop)
s.ref = ref
return nil
}
// Stop stops underlying FSEventStream and unregisters it from global runloop.
func (s *stream) Stop() {
if s.ref == nilstream {
return
}
wg.Wait()
C.FSEventStreamStop(s.ref)
C.FSEventStreamInvalidate(s.ref)
C.CFRunLoopWakeUp(runloop)
s.ref = nilstream
streamFuncs.delete(s.info)
}
+111
View File
@@ -0,0 +1,111 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin,!kqueue
package notify
import (
"reflect"
"testing"
)
func TestSplitflags(t *testing.T) {
cases := [...]struct {
set uint32
flags []uint32
}{
{0, nil},
{0xD, []uint32{0x1, 0x4, 0x8}},
{0x0010 | 0x0040 | 0x0080 | 0x01000, []uint32{0x0010, 0x0040, 0x0080, 0x01000}},
{0x40000 | 0x00100 | 0x00200, []uint32{0x00100, 0x00200, 0x40000}},
}
for i, cas := range cases {
if flags := splitflags(cas.set); !reflect.DeepEqual(flags, cas.flags) {
t.Errorf("want flags=%v; got %v (i=%d)", cas.flags, flags, i)
}
}
}
func TestWatchStrip(t *testing.T) {
const (
create = uint32(FSEventsCreated)
remove = uint32(FSEventsRemoved)
rename = uint32(FSEventsRenamed)
write = uint32(FSEventsModified)
inode = uint32(FSEventsInodeMetaMod)
owner = uint32(FSEventsChangeOwner)
)
cases := [...][]struct {
path string
flag uint32
diff uint32
}{
// 1.
{
{"file", create | write, create | write},
{"file", create | write | inode, write | inode},
},
// 2.
{
{"file", create, create},
{"file", create | remove, remove},
{"file", create | remove, create},
},
// 3.
{
{"file", create | write, create | write},
{"file", create | write | owner, write | owner},
},
// 4.
{
{"file", create | write, create | write},
{"file", write | inode, write | inode},
{"file", remove | write | inode, remove},
},
{
{"file", remove | write | inode, remove},
},
}
Test:
for i, cas := range cases {
if len(cas) == 0 {
t.Log("skipped")
continue
}
w := &watch{prev: make(map[string]uint32)}
for j, cas := range cas {
if diff := w.strip(cas.path, cas.flag); diff != cas.diff {
t.Errorf("want diff=%v; got %v (i=%d, j=%d)", Event(cas.diff),
Event(diff), i, j)
continue Test
}
}
}
}
// Test for cases 3) and 5) with shadowed write&create events.
//
// See comment for (flagdiff).diff method.
func TestWatcherShadowedWriteCreate(t *testing.T) {
w := NewWatcherTest(t, "testdata/vfs.txt")
defer w.Close()
cases := [...]WCase{
// i=0
create(w, "src/github.com/rjeczalik/fs/.fs.go.swp"),
// i=1
write(w, "src/github.com/rjeczalik/fs/.fs.go.swp", []byte("XD")),
// i=2
write(w, "src/github.com/rjeczalik/fs/.fs.go.swp", []byte("XD")),
// i=3
remove(w, "src/github.com/rjeczalik/fs/.fs.go.swp"),
// i=4
create(w, "src/github.com/rjeczalik/fs/.fs.go.swp"),
// i=5
write(w, "src/github.com/rjeczalik/fs/.fs.go.swp", []byte("XD")),
}
w.ExpectAny(cases[:5]) // BUG(rjeczalik): #62
}
+405
View File
@@ -0,0 +1,405 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build linux
package notify
import (
"bytes"
"errors"
"path/filepath"
"runtime"
"sync"
"sync/atomic"
"unsafe"
"golang.org/x/sys/unix"
)
// eventBufferSize defines the size of the buffer given to read(2) function. One
// should not depend on this value, since it was arbitrary chosen and may be
// changed in the future.
const eventBufferSize = 64 * (unix.SizeofInotifyEvent + unix.PathMax + 1)
// consumersCount defines the number of consumers in producer-consumer based
// implementation. Each consumer is run in a separate goroutine and has read
// access to watched files map.
const consumersCount = 2
const invalidDescriptor = -1
// watched is a pair of file path and inotify mask used as a value in
// watched files map.
type watched struct {
path string
mask uint32
}
// inotify implements Watcher interface.
type inotify struct {
sync.RWMutex // protects inotify.m map
m map[int32]*watched // watch descriptor to watched object
fd int32 // inotify file descriptor
pipefd []int // pipe's read and write descriptors
epfd int // epoll descriptor
epes []unix.EpollEvent // epoll events
buffer [eventBufferSize]byte // inotify event buffer
wg sync.WaitGroup // wait group used to close main loop
c chan<- EventInfo // event dispatcher channel
}
// NewWatcher creates new non-recursive inotify backed by inotify.
func newWatcher(c chan<- EventInfo) watcher {
i := &inotify{
m: make(map[int32]*watched),
fd: invalidDescriptor,
pipefd: []int{invalidDescriptor, invalidDescriptor},
epfd: invalidDescriptor,
epes: make([]unix.EpollEvent, 0),
c: c,
}
runtime.SetFinalizer(i, func(i *inotify) {
i.epollclose()
if i.fd != invalidDescriptor {
unix.Close(int(i.fd))
}
})
return i
}
// Watch implements notify.watcher interface.
func (i *inotify) Watch(path string, e Event) error {
return i.watch(path, e)
}
// Rewatch implements notify.watcher interface.
func (i *inotify) Rewatch(path string, _, newevent Event) error {
return i.watch(path, newevent)
}
// watch adds a new watcher to the set of watched objects or modifies the existing
// one. If called for the first time, this function initializes inotify filesystem
// monitor and starts producer-consumers goroutines.
func (i *inotify) watch(path string, e Event) (err error) {
if e&^(All|Event(unix.IN_ALL_EVENTS)) != 0 {
return errors.New("notify: unknown event")
}
if err = i.lazyinit(); err != nil {
return
}
iwd, err := unix.InotifyAddWatch(int(i.fd), path, encode(e))
if err != nil {
return
}
i.RLock()
wd := i.m[int32(iwd)]
i.RUnlock()
if wd == nil {
i.Lock()
if i.m[int32(iwd)] == nil {
i.m[int32(iwd)] = &watched{path: path, mask: uint32(e)}
}
i.Unlock()
} else {
i.Lock()
wd.mask = uint32(e)
i.Unlock()
}
return nil
}
// lazyinit sets up all required file descriptors and starts 1+consumersCount
// goroutines. The producer goroutine blocks until file-system notifications
// occur. Then, all events are read from system buffer and sent to consumer
// goroutines which construct valid notify events. This method uses
// Double-Checked Locking optimization.
func (i *inotify) lazyinit() error {
if atomic.LoadInt32(&i.fd) == invalidDescriptor {
i.Lock()
defer i.Unlock()
if atomic.LoadInt32(&i.fd) == invalidDescriptor {
fd, err := unix.InotifyInit1(unix.IN_CLOEXEC)
if err != nil {
return err
}
i.fd = int32(fd)
if err = i.epollinit(); err != nil {
_, _ = i.epollclose(), unix.Close(int(fd)) // Ignore errors.
i.fd = invalidDescriptor
return err
}
esch := make(chan []*event)
go i.loop(esch)
i.wg.Add(consumersCount)
for n := 0; n < consumersCount; n++ {
go i.send(esch)
}
}
}
return nil
}
// epollinit opens an epoll file descriptor and creates a pipe which will be
// used to wake up the epoll_wait(2) function. Then, file descriptor associated
// with inotify event queue and the read end of the pipe are added to epoll set.
// Note that `fd` member must be set before this function is called.
func (i *inotify) epollinit() (err error) {
if i.epfd, err = unix.EpollCreate1(0); err != nil {
return
}
if err = unix.Pipe(i.pipefd); err != nil {
return
}
i.epes = []unix.EpollEvent{
{Events: unix.EPOLLIN, Fd: i.fd},
{Events: unix.EPOLLIN, Fd: int32(i.pipefd[0])},
}
if err = unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, int(i.fd), &i.epes[0]); err != nil {
return
}
return unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, i.pipefd[0], &i.epes[1])
}
// epollclose closes the file descriptor created by the call to epoll_create(2)
// and two file descriptors opened by pipe(2) function.
func (i *inotify) epollclose() (err error) {
if i.epfd != invalidDescriptor {
if err = unix.Close(i.epfd); err == nil {
i.epfd = invalidDescriptor
}
}
for n, fd := range i.pipefd {
if fd != invalidDescriptor {
switch e := unix.Close(fd); {
case e != nil && err == nil:
err = e
case e == nil:
i.pipefd[n] = invalidDescriptor
}
}
}
return
}
// loop blocks until either inotify or pipe file descriptor is ready for I/O.
// All read operations triggered by filesystem notifications are forwarded to
// one of the event's consumers. If pipe fd became ready, loop function closes
// all file descriptors opened by lazyinit method and returns afterwards.
func (i *inotify) loop(esch chan<- []*event) {
epes := make([]unix.EpollEvent, 1)
fd := atomic.LoadInt32(&i.fd)
for {
switch _, err := unix.EpollWait(i.epfd, epes, -1); err {
case nil:
switch epes[0].Fd {
case fd:
esch <- i.read()
epes[0].Fd = 0
case int32(i.pipefd[0]):
i.Lock()
defer i.Unlock()
if err = unix.Close(int(fd)); err != nil && err != unix.EINTR {
panic("notify: close(2) error " + err.Error())
}
atomic.StoreInt32(&i.fd, invalidDescriptor)
if err = i.epollclose(); err != nil && err != unix.EINTR {
panic("notify: epollclose error " + err.Error())
}
close(esch)
return
}
case unix.EINTR:
continue
default: // We should never reach this line.
panic("notify: epoll_wait(2) error " + err.Error())
}
}
}
// read reads events from an inotify file descriptor. It does not handle errors
// returned from read(2) function since they are not critical to watcher logic.
func (i *inotify) read() (es []*event) {
n, err := unix.Read(int(i.fd), i.buffer[:])
if err != nil || n < unix.SizeofInotifyEvent {
return
}
var sys *unix.InotifyEvent
nmin := n - unix.SizeofInotifyEvent
for pos, path := 0, ""; pos <= nmin; {
sys = (*unix.InotifyEvent)(unsafe.Pointer(&i.buffer[pos]))
pos += unix.SizeofInotifyEvent
if path = ""; sys.Len > 0 {
endpos := pos + int(sys.Len)
path = string(bytes.TrimRight(i.buffer[pos:endpos], "\x00"))
pos = endpos
}
es = append(es, &event{
sys: unix.InotifyEvent{
Wd: sys.Wd,
Mask: sys.Mask,
Cookie: sys.Cookie,
},
path: path,
})
}
return
}
// send is a consumer function which sends events to event dispatcher channel.
// It is run in a separate goroutine in order to not block loop method when
// possibly expensive write operations are performed on inotify map.
func (i *inotify) send(esch <-chan []*event) {
for es := range esch {
for _, e := range i.transform(es) {
if e != nil {
i.c <- e
}
}
}
i.wg.Done()
}
// transform prepares events read from inotify file descriptor for sending to
// user. It removes invalid events and these which are no longer present in
// inotify map. This method may also split one raw event into two different ones
// when system-dependent result is required.
func (i *inotify) transform(es []*event) []*event {
var multi []*event
i.RLock()
for idx, e := range es {
if e.sys.Mask&(unix.IN_IGNORED|unix.IN_Q_OVERFLOW) != 0 {
es[idx] = nil
continue
}
wd, ok := i.m[e.sys.Wd]
if !ok || e.sys.Mask&encode(Event(wd.mask)) == 0 {
es[idx] = nil
continue
}
if e.path == "" {
e.path = wd.path
} else {
e.path = filepath.Join(wd.path, e.path)
}
multi = append(multi, decode(Event(wd.mask), e))
if e.event == 0 {
es[idx] = nil
}
}
i.RUnlock()
es = append(es, multi...)
return es
}
// encode converts notify system-independent events to valid inotify mask
// which can be passed to inotify_add_watch(2) function.
func encode(e Event) uint32 {
if e&Create != 0 {
e = (e ^ Create) | InCreate | InMovedTo
}
if e&Remove != 0 {
e = (e ^ Remove) | InDelete | InDeleteSelf
}
if e&Write != 0 {
e = (e ^ Write) | InModify
}
if e&Rename != 0 {
e = (e ^ Rename) | InMovedFrom | InMoveSelf
}
return uint32(e)
}
// decode uses internally stored mask to distinguish whether system-independent
// or system-dependent event is requested. The first one is created by modifying
// `e` argument. decode method sets e.event value to 0 when an event should be
// skipped. System-dependent event is set as the function's return value which
// can be nil when the event should not be passed on.
func decode(mask Event, e *event) (syse *event) {
if sysmask := uint32(mask) & e.sys.Mask; sysmask != 0 {
syse = &event{sys: unix.InotifyEvent{
Wd: e.sys.Wd,
Mask: e.sys.Mask,
Cookie: e.sys.Cookie,
}, event: Event(sysmask), path: e.path}
}
imask := encode(mask)
switch {
case mask&Create != 0 && imask&uint32(InCreate|InMovedTo)&e.sys.Mask != 0:
e.event = Create
case mask&Remove != 0 && imask&uint32(InDelete|InDeleteSelf)&e.sys.Mask != 0:
e.event = Remove
case mask&Write != 0 && imask&uint32(InModify)&e.sys.Mask != 0:
e.event = Write
case mask&Rename != 0 && imask&uint32(InMovedFrom|InMoveSelf)&e.sys.Mask != 0:
e.event = Rename
default:
e.event = 0
}
return
}
// Unwatch implements notify.watcher interface. It looks for watch descriptor
// related to registered path and if found, calls inotify_rm_watch(2) function.
// This method is allowed to return EINVAL error when concurrently requested to
// delete identical path.
func (i *inotify) Unwatch(path string) (err error) {
iwd := int32(invalidDescriptor)
i.RLock()
for iwdkey, wd := range i.m {
if wd.path == path {
iwd = iwdkey
break
}
}
i.RUnlock()
if iwd == invalidDescriptor {
return errors.New("notify: path " + path + " is already watched")
}
fd := atomic.LoadInt32(&i.fd)
if err = removeInotifyWatch(fd, iwd); err != nil {
return
}
i.Lock()
delete(i.m, iwd)
i.Unlock()
return nil
}
// Close implements notify.watcher interface. It removes all existing watch
// descriptors and wakes up producer goroutine by sending data to the write end
// of the pipe. The function waits for a signal from producer which means that
// all operations on current monitoring instance are done.
func (i *inotify) Close() (err error) {
i.Lock()
if fd := atomic.LoadInt32(&i.fd); fd == invalidDescriptor {
i.Unlock()
return nil
}
for iwd := range i.m {
if e := removeInotifyWatch(i.fd, iwd); e != nil && err == nil {
err = e
}
delete(i.m, iwd)
}
switch _, errwrite := unix.Write(i.pipefd[1], []byte{0x00}); {
case errwrite != nil && err == nil:
err = errwrite
fallthrough
case errwrite != nil:
i.Unlock()
default:
i.Unlock()
i.wg.Wait()
}
return
}
// if path was removed, notify already removed the watch and returns EINVAL error
func removeInotifyWatch(fd int32, iwd int32) (err error) {
if _, err = unix.InotifyRmWatch(int(fd), uint32(iwd)); err != nil && err != unix.EINVAL {
return
}
return nil
}
+132
View File
@@ -0,0 +1,132 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build linux
package notify
import (
"os"
"path/filepath"
"testing"
)
func icreate(w *W, path string) WCase {
cas := create(w, path)
cas.Events = append(cas.Events,
&Call{P: path, E: InCreate},
)
return cas
}
func iremove(w *W, path string) WCase {
cas := remove(w, path)
cas.Events = append(cas.Events,
&Call{P: path, E: InDelete},
)
return cas
}
func iopen(w *W, path string) WCase {
return WCase{
Action: func() {
f, err := os.OpenFile(filepath.Join(w.root, path), os.O_RDWR, 0644)
if err != nil {
w.Fatalf("OpenFile(%q)=%v", path, err)
}
if err := f.Close(); err != nil {
w.Fatalf("Close(%q)=%v", path, err)
}
},
Events: []EventInfo{
&Call{P: path, E: InAccess},
&Call{P: path, E: InOpen},
&Call{P: path, E: InCloseNowrite},
},
}
}
func iread(w *W, path string, p []byte) WCase {
return WCase{
Action: func() {
f, err := os.OpenFile(filepath.Join(w.root, path), os.O_RDWR, 0644)
if err != nil {
w.Fatalf("OpenFile(%q)=%v", path, err)
}
if _, err := f.Read(p); err != nil {
w.Fatalf("Read(%q)=%v", path, err)
}
if err := f.Close(); err != nil {
w.Fatalf("Close(%q)=%v", path, err)
}
},
Events: []EventInfo{
&Call{P: path, E: InAccess},
&Call{P: path, E: InOpen},
&Call{P: path, E: InModify},
&Call{P: path, E: InCloseNowrite},
},
}
}
func iwrite(w *W, path string, p []byte) WCase {
cas := write(w, path, p)
path = cas.Events[0].Path()
cas.Events = append(cas.Events,
&Call{P: path, E: InAccess},
&Call{P: path, E: InOpen},
&Call{P: path, E: InModify},
&Call{P: path, E: InCloseWrite},
)
return cas
}
func irename(w *W, path string) WCase {
const ext = ".notify"
return WCase{
Action: func() {
file := filepath.Join(w.root, path)
if err := os.Rename(file, file+ext); err != nil {
w.Fatalf("Rename(%q, %q)=%v", path, path+ext, err)
}
},
Events: []EventInfo{
&Call{P: path, E: InMovedFrom},
&Call{P: path + ext, E: InMovedTo},
&Call{P: path, E: InOpen},
&Call{P: path, E: InAccess},
&Call{P: path, E: InCreate},
},
}
}
var events = []Event{
InAccess,
InModify,
InAttrib,
InCloseWrite,
InCloseNowrite,
InOpen,
InMovedFrom,
InMovedTo,
InCreate,
InDelete,
InDeleteSelf,
InMoveSelf,
}
func TestWatcherInotify(t *testing.T) {
w := NewWatcherTest(t, "testdata/vfs.txt", events...)
defer w.Close()
cases := [...]WCase{
iopen(w, "src/github.com/rjeczalik/fs/fs.go"),
iwrite(w, "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
iread(w, "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
iremove(w, "src/github.com/ppknap/link/README.md"),
irename(w, "src/github.com/rjeczalik/fs/LICENSE"),
}
w.ExpectAny(cases[:])
}
+189
View File
@@ -0,0 +1,189 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin,kqueue dragonfly freebsd netbsd openbsd
package notify
import (
"fmt"
"os"
"syscall"
)
// newTrigger returns implementation of trigger.
func newTrigger(pthLkp map[string]*watched) trigger {
return &kq{
pthLkp: pthLkp,
idLkp: make(map[int]*watched),
}
}
// kq is a structure implementing trigger for kqueue.
type kq struct {
// fd is a kqueue file descriptor
fd int
// pipefds are file descriptors used to stop `Kevent` call.
pipefds [2]int
// idLkp is a data structure mapping file descriptors with data about watching
// represented by them files/directories.
idLkp map[int]*watched
// pthLkp is a structure mapping monitored files/dir with data about them,
// shared with parent trg structure
pthLkp map[string]*watched
}
// watched is a data structure representing watched file/directory.
type watched struct {
trgWatched
// fd is a file descriptor for watched file/directory.
fd int
}
// Stop implements trigger.
func (k *kq) Stop() (err error) {
// trigger event used to interrupt Kevent call.
_, err = syscall.Write(k.pipefds[1], []byte{0x00})
return
}
// Close implements trigger.
func (k *kq) Close() error {
return syscall.Close(k.fd)
}
// NewWatched implements trigger.
func (*kq) NewWatched(p string, fi os.FileInfo) (*watched, error) {
fd, err := syscall.Open(p, syscall.O_NONBLOCK|syscall.O_RDONLY, 0)
if err != nil {
return nil, err
}
return &watched{
trgWatched: trgWatched{p: p, fi: fi},
fd: fd,
}, nil
}
// Record implements trigger.
func (k *kq) Record(w *watched) {
k.idLkp[w.fd], k.pthLkp[w.p] = w, w
}
// Del implements trigger.
func (k *kq) Del(w *watched) {
syscall.Close(w.fd)
delete(k.idLkp, w.fd)
delete(k.pthLkp, w.p)
}
func inter2kq(n interface{}) syscall.Kevent_t {
kq, ok := n.(syscall.Kevent_t)
if !ok {
panic(fmt.Sprintf("kqueue: type should be Kevent_t, %T instead", n))
}
return kq
}
// Init implements trigger.
func (k *kq) Init() (err error) {
if k.fd, err = syscall.Kqueue(); err != nil {
return
}
// Creates pipe used to stop `Kevent` call by registering it,
// watching read end and writing to other end of it.
if err = syscall.Pipe(k.pipefds[:]); err != nil {
return nonil(err, k.Close())
}
var kevn [1]syscall.Kevent_t
syscall.SetKevent(&kevn[0], k.pipefds[0], syscall.EVFILT_READ, syscall.EV_ADD)
if _, err = syscall.Kevent(k.fd, kevn[:], nil, nil); err != nil {
return nonil(err, k.Close())
}
return
}
// Unwatch implements trigger.
func (k *kq) Unwatch(w *watched) (err error) {
var kevn [1]syscall.Kevent_t
syscall.SetKevent(&kevn[0], w.fd, syscall.EVFILT_VNODE, syscall.EV_DELETE)
_, err = syscall.Kevent(k.fd, kevn[:], nil, nil)
return
}
// Watch implements trigger.
func (k *kq) Watch(fi os.FileInfo, w *watched, e int64) (err error) {
var kevn [1]syscall.Kevent_t
syscall.SetKevent(&kevn[0], w.fd, syscall.EVFILT_VNODE,
syscall.EV_ADD|syscall.EV_CLEAR)
kevn[0].Fflags = uint32(e)
_, err = syscall.Kevent(k.fd, kevn[:], nil, nil)
return
}
// Wait implements trigger.
func (k *kq) Wait() (interface{}, error) {
var (
kevn [1]syscall.Kevent_t
err error
)
kevn[0] = syscall.Kevent_t{}
_, err = syscall.Kevent(k.fd, nil, kevn[:], nil)
return kevn[0], err
}
// Watched implements trigger.
func (k *kq) Watched(n interface{}) (*watched, int64, error) {
kevn, ok := n.(syscall.Kevent_t)
if !ok {
panic(fmt.Sprintf("kq: type should be syscall.Kevent_t, %T instead", kevn))
}
if _, ok = k.idLkp[int(kevn.Ident)]; !ok {
return nil, 0, errNotWatched
}
return k.idLkp[int(kevn.Ident)], int64(kevn.Fflags), nil
}
// IsStop implements trigger.
func (k *kq) IsStop(n interface{}, err error) bool {
return int(inter2kq(n).Ident) == k.pipefds[0]
}
func init() {
encode = func(e Event, dir bool) (o int64) {
// Create event is not supported by kqueue. Instead NoteWrite event will
// be registered for a directory. If this event will be reported on dir
// which is to be monitored for Create, dir will be rescanned
// and Create events will be generated and returned for new files.
// In case of files, if not requested NoteRename event is reported,
// it will be ignored.
o = int64(e &^ Create)
if (e&Create != 0 && dir) || e&Write != 0 {
o = (o &^ int64(Write)) | int64(NoteWrite)
}
if e&Rename != 0 {
o = (o &^ int64(Rename)) | int64(NoteRename)
}
if e&Remove != 0 {
o = (o &^ int64(Remove)) | int64(NoteDelete)
}
return
}
nat2not = map[Event]Event{
NoteWrite: Write,
NoteRename: Rename,
NoteDelete: Remove,
NoteExtend: Event(0),
NoteAttrib: Event(0),
NoteRevoke: Event(0),
NoteLink: Event(0),
}
not2nat = map[Event]Event{
Write: NoteWrite,
Rename: NoteRename,
Remove: NoteDelete,
}
}
+111
View File
@@ -0,0 +1,111 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin,kqueue dragonfly freebsd netbsd openbsd
package notify
import (
"os"
"path/filepath"
"testing"
)
func kqremove(w *W, path string, files []string) WCase {
cas := remove(w, path)
cas.Events[0] = &Call{P: path, E: NoteDelete}
for _, f := range files {
cas.Events = append(cas.Events, &Call{P: f, E: NoteDelete})
}
return cas
}
func kqwrite(w *W, path string, p []byte) WCase {
cas := write(w, path, p)
path = cas.Events[0].Path()
cas.Events[0] = &Call{P: path, E: NoteExtend | NoteWrite}
return cas
}
func kqrename(w *W, path string, files []string) WCase {
const ext = ".notify"
cas := WCase{
Action: func() {
file := filepath.Join(w.root, path)
if err := os.Rename(file, file+ext); err != nil {
w.Fatalf("Rename(%q, %q)=%v", path, path+ext, err)
}
},
Events: []EventInfo{
&Call{P: path + ext, E: osSpecificCreate},
&Call{P: path, E: NoteRename},
},
}
for _, f := range files {
cas.Events = append(cas.Events, &Call{P: f, E: NoteRename})
}
return cas
}
func kqlink(w *W, path string) WCase {
const ext = ".notify"
return WCase{
Action: func() {
file := filepath.Join(w.root, path)
if err := os.Link(file, file+ext); err != nil {
w.Fatalf("Link(%q, %q)=%v", path, path+ext, err)
}
},
Events: []EventInfo{
&Call{P: path, E: NoteLink},
&Call{P: path + ext, E: osSpecificCreate},
},
}
}
var events = []Event{
NoteWrite,
NoteAttrib,
NoteRename,
osSpecificCreate,
NoteDelete,
NoteExtend,
NoteLink,
}
func TestWatcherKqueue(t *testing.T) {
w := NewWatcherTest(t, "testdata/vfs.txt", events...)
defer w.Close()
cases := [...]WCase{
kqremove(w, "src/github.com/ppknap/link/include/coost/link", []string{
"src/github.com/ppknap/link/include/coost/link/definitions.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/bundle.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/container_invoker.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/container_value_trait.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/dummy_type.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/function_trait.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/immediate_invoker.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/stdhelpers/always_same.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/stdhelpers/make_unique.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/stdhelpers",
"src/github.com/ppknap/link/include/coost/link/detail/vertex.hpp",
"src/github.com/ppknap/link/include/coost/link/detail/wire.hpp",
"src/github.com/ppknap/link/include/coost/link/detail",
"src/github.com/ppknap/link/include/coost/link/link.hpp",
},
),
kqwrite(w, "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
kqremove(w, "src/github.com/ppknap/link/README.md", nil),
kqlink(w, "src/github.com/rjeczalik/fs/LICENSE"),
kqrename(w, "src/github.com/rjeczalik/fs/fs.go", nil),
kqrename(w, "src/github.com/rjeczalik/fs/cmd/gotree", []string{
"src/github.com/rjeczalik/fs/cmd/gotree/go.go",
"src/github.com/rjeczalik/fs/cmd/gotree/main.go",
},
),
}
w.ExpectAll(cases[:])
}
+15
View File
@@ -0,0 +1,15 @@
// Copyright (c) 2014-2018 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build !darwin,!linux,!freebsd,!dragonfly,!netbsd,!openbsd,!windows
// +build !kqueue,!solaris
package notify
import "errors"
// newWatcher stub.
func newWatcher(chan<- EventInfo) watcher {
return watcherStub{errors.New("notify: not implemented")}
}
+605
View File
@@ -0,0 +1,605 @@
// Copyright (c) 2014-2018 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build windows
package notify
import (
"errors"
"runtime"
"sync"
"sync/atomic"
"syscall"
"unsafe"
)
// readBufferSize defines the size of an array in which read statuses are stored.
// The buffer have to be DWORD-aligned and, if notify is used in monitoring a
// directory over the network, its size must not be greater than 64KB. Each of
// watched directories uses its own buffer for storing events.
const readBufferSize = 4096
// Since all operations which go through the Windows completion routine are done
// asynchronously, filter may set one of the constants below. They were defined
// in order to distinguish whether current folder should be re-registered in
// ReadDirectoryChangesW function or some control operations need to be executed.
const (
stateRewatch uint32 = 1 << (28 + iota)
stateUnwatch
stateCPClose
)
// Filter used in current implementation was split into four segments:
// - bits 0-11 store ReadDirectoryChangesW filters,
// - bits 12-19 store File notify actions,
// - bits 20-27 store notify specific events and flags,
// - bits 28-31 store states which are used in loop's FSM.
// Constants below are used as masks to retrieve only specific filter parts.
const (
onlyNotifyChanges uint32 = 0x00000FFF
onlyNGlobalEvents uint32 = 0x0FF00000
onlyMachineStates uint32 = 0xF0000000
)
// grip represents a single watched directory. It stores the data required by
// ReadDirectoryChangesW function. Only the filter, recursive, and handle members
// may by modified by watcher implementation. Rest of the them have to remain
// constant since they are used by Windows completion routine. This indicates that
// grip can be removed only when all operations on the file handle are finished.
type grip struct {
handle syscall.Handle
filter uint32
recursive bool
pathw []uint16
buffer [readBufferSize]byte
parent *watched
ovlapped *overlappedEx
}
// overlappedEx stores information used in asynchronous input and output.
// Additionally, overlappedEx contains a pointer to 'grip' item which is used in
// order to gather the structure in which the overlappedEx object was created.
type overlappedEx struct {
syscall.Overlapped
parent *grip
}
// newGrip creates a new file handle that can be used in overlapped operations.
// Then, the handle is associated with I/O completion port 'cph' and its value
// is stored in newly created 'grip' object.
func newGrip(cph syscall.Handle, parent *watched, filter uint32) (*grip, error) {
g := &grip{
handle: syscall.InvalidHandle,
filter: filter,
recursive: parent.recursive,
pathw: parent.pathw,
parent: parent,
ovlapped: &overlappedEx{},
}
if err := g.register(cph); err != nil {
return nil, err
}
g.ovlapped.parent = g
return g, nil
}
// NOTE : Thread safe
func (g *grip) register(cph syscall.Handle) (err error) {
if g.handle, err = syscall.CreateFile(
&g.pathw[0],
syscall.FILE_LIST_DIRECTORY,
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
nil,
syscall.OPEN_EXISTING,
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED,
0,
); err != nil {
return
}
if _, err = syscall.CreateIoCompletionPort(g.handle, cph, 0, 0); err != nil {
syscall.CloseHandle(g.handle)
return
}
return g.readDirChanges()
}
// readDirChanges tells the system to store file change information in grip's
// buffer. Directory changes that occur between calls to this function are added
// to the buffer and then, returned with the next call.
func (g *grip) readDirChanges() error {
handle := syscall.Handle(atomic.LoadUintptr((*uintptr)(&g.handle)))
if handle == syscall.InvalidHandle {
return nil // Handle was closed.
}
return syscall.ReadDirectoryChanges(
handle,
&g.buffer[0],
uint32(unsafe.Sizeof(g.buffer)),
g.recursive,
encode(g.filter),
nil,
(*syscall.Overlapped)(unsafe.Pointer(g.ovlapped)),
0,
)
}
// encode transforms a generic filter, which contains platform independent and
// implementation specific bit fields, to value that can be used as NotifyFilter
// parameter in ReadDirectoryChangesW function.
func encode(filter uint32) uint32 {
e := Event(filter & (onlyNGlobalEvents | onlyNotifyChanges))
if e&dirmarker != 0 {
return uint32(FileNotifyChangeDirName)
}
if e&Create != 0 {
e = (e ^ Create) | FileNotifyChangeFileName
}
if e&Remove != 0 {
e = (e ^ Remove) | FileNotifyChangeFileName
}
if e&Write != 0 {
e = (e ^ Write) | FileNotifyChangeAttributes | FileNotifyChangeSize |
FileNotifyChangeCreation | FileNotifyChangeSecurity
}
if e&Rename != 0 {
e = (e ^ Rename) | FileNotifyChangeFileName
}
return uint32(e)
}
// watched is made in order to check whether an action comes from a directory or
// file. This approach requires two file handlers per single monitored folder. The
// second grip handles actions which include creating or deleting a directory. If
// these processes are not monitored, only the first grip is created.
type watched struct {
filter uint32
recursive bool
count uint8
pathw []uint16
digrip [2]*grip
}
// newWatched creates a new watched instance. It splits the filter variable into
// two parts. The first part is responsible for watching all events which can be
// created for a file in watched directory structure and the second one watches
// only directory Create/Remove actions. If all operations succeed, the Create
// message is sent to I/O completion port queue for further processing.
func newWatched(cph syscall.Handle, filter uint32, recursive bool,
path string) (wd *watched, err error) {
wd = &watched{
filter: filter,
recursive: recursive,
}
if wd.pathw, err = syscall.UTF16FromString(path); err != nil {
return
}
if err = wd.recreate(cph); err != nil {
return
}
return wd, nil
}
// TODO : doc
func (wd *watched) recreate(cph syscall.Handle) (err error) {
filefilter := wd.filter &^ uint32(FileNotifyChangeDirName)
if err = wd.updateGrip(0, cph, filefilter == 0, filefilter); err != nil {
return
}
dirfilter := wd.filter & uint32(FileNotifyChangeDirName|Create|Remove)
if err = wd.updateGrip(1, cph, dirfilter == 0, wd.filter|uint32(dirmarker)); err != nil {
return
}
wd.filter &^= onlyMachineStates
return
}
// TODO : doc
func (wd *watched) updateGrip(idx int, cph syscall.Handle, reset bool,
newflag uint32) (err error) {
if reset {
wd.digrip[idx] = nil
} else {
if wd.digrip[idx] == nil {
if wd.digrip[idx], err = newGrip(cph, wd, newflag); err != nil {
wd.closeHandle()
return
}
} else {
wd.digrip[idx].filter = newflag
wd.digrip[idx].recursive = wd.recursive
if err = wd.digrip[idx].register(cph); err != nil {
wd.closeHandle()
return
}
}
wd.count++
}
return
}
// closeHandle closes handles that are stored in digrip array. Function always
// tries to close all of the handlers before it exits, even when there are errors
// returned from the operating system kernel.
func (wd *watched) closeHandle() (err error) {
for _, g := range wd.digrip {
if g == nil {
continue
}
for {
handle := syscall.Handle(atomic.LoadUintptr((*uintptr)(&g.handle)))
if handle == syscall.InvalidHandle {
break // Already closed.
}
e := syscall.CloseHandle(handle)
if e != nil && err == nil {
err = e
}
// Set invalid handle even when CloseHandle fails. This will leak
// the handle but, since we can't close it anyway, there won't be
// any difference.
if atomic.CompareAndSwapUintptr((*uintptr)(&g.handle),
(uintptr)(handle), (uintptr)(syscall.InvalidHandle)) {
break
}
}
}
return
}
// watcher implements Watcher interface. It stores a set of watched directories.
// All operations which remove watched objects from map `m` must be performed in
// loop goroutine since these structures are used internally by operating system.
type readdcw struct {
sync.Mutex
m map[string]*watched
cph syscall.Handle
start bool
wg sync.WaitGroup
c chan<- EventInfo
}
// NewWatcher creates new non-recursive watcher backed by ReadDirectoryChangesW.
func newWatcher(c chan<- EventInfo) watcher {
r := &readdcw{
m: make(map[string]*watched),
cph: syscall.InvalidHandle,
c: c,
}
runtime.SetFinalizer(r, func(r *readdcw) {
if r.cph != syscall.InvalidHandle {
syscall.CloseHandle(r.cph)
}
})
return r
}
// Watch implements notify.Watcher interface.
func (r *readdcw) Watch(path string, event Event) error {
return r.watch(path, event, false)
}
// RecursiveWatch implements notify.RecursiveWatcher interface.
func (r *readdcw) RecursiveWatch(path string, event Event) error {
return r.watch(path, event, true)
}
// watch inserts a directory to the group of watched folders. If watched folder
// already exists, function tries to rewatch it with new filters(NOT VALID). Moreover,
// watch starts the main event loop goroutine when called for the first time.
func (r *readdcw) watch(path string, event Event, recursive bool) error {
if event&^(All|fileNotifyChangeAll) != 0 {
return errors.New("notify: unknown event")
}
r.Lock()
defer r.Unlock()
if wd, ok := r.m[path]; ok {
dbgprint("watch: already exists")
wd.filter &^= stateUnwatch
return nil
}
if err := r.lazyinit(); err != nil {
return err
}
wd, err := newWatched(r.cph, uint32(event), recursive, path)
if err != nil {
return err
}
r.m[path] = wd
dbgprint("watch: new watch added")
return nil
}
// lazyinit creates an I/O completion port and starts the main event loop.
func (r *readdcw) lazyinit() (err error) {
invalid := uintptr(syscall.InvalidHandle)
if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid {
cph := syscall.InvalidHandle
if cph, err = syscall.CreateIoCompletionPort(cph, 0, 0, 0); err != nil {
return
}
r.cph, r.start = cph, true
go r.loop()
}
return
}
// TODO(pknap) : doc
func (r *readdcw) loop() {
var n, key uint32
var overlapped *syscall.Overlapped
for {
err := syscall.GetQueuedCompletionStatus(r.cph, &n, &key, &overlapped, syscall.INFINITE)
if key == stateCPClose {
r.Lock()
handle := r.cph
r.cph = syscall.InvalidHandle
r.Unlock()
syscall.CloseHandle(handle)
r.wg.Done()
return
}
if overlapped == nil {
// TODO: check key == rewatch delete or 0(panic)
continue
}
overEx := (*overlappedEx)(unsafe.Pointer(overlapped))
if n != 0 {
r.loopevent(n, overEx)
if err = overEx.parent.readDirChanges(); err != nil {
// TODO: error handling
}
}
r.loopstate(overEx)
}
}
// TODO(pknap) : doc
func (r *readdcw) loopstate(overEx *overlappedEx) {
r.Lock()
defer r.Unlock()
filter := overEx.parent.parent.filter
if filter&onlyMachineStates == 0 {
return
}
if overEx.parent.parent.count--; overEx.parent.parent.count == 0 {
switch filter & onlyMachineStates {
case stateRewatch:
dbgprint("loopstate rewatch")
overEx.parent.parent.recreate(r.cph)
case stateUnwatch:
dbgprint("loopstate unwatch")
overEx.parent.parent.closeHandle()
delete(r.m, syscall.UTF16ToString(overEx.parent.pathw))
case stateCPClose:
default:
panic(`notify: windows loopstate logic error`)
}
}
}
// TODO(pknap) : doc
func (r *readdcw) loopevent(n uint32, overEx *overlappedEx) {
events := []*event{}
var currOffset uint32
for {
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&overEx.parent.buffer[currOffset]))
name := syscall.UTF16ToString((*[syscall.MAX_LONG_PATH]uint16)(unsafe.Pointer(&raw.FileName))[:raw.FileNameLength>>1])
events = append(events, &event{
pathw: overEx.parent.pathw,
filter: overEx.parent.filter,
action: raw.Action,
name: name,
})
if raw.NextEntryOffset == 0 {
break
}
if currOffset += raw.NextEntryOffset; currOffset >= n {
break
}
}
r.send(events)
}
// TODO(pknap) : doc
func (r *readdcw) send(es []*event) {
for _, e := range es {
var syse Event
if e.e, syse = decode(e.filter, e.action); e.e == 0 && syse == 0 {
continue
}
switch {
case e.action == syscall.FILE_ACTION_MODIFIED:
e.ftype = fTypeUnknown
case e.filter&uint32(dirmarker) != 0:
e.ftype = fTypeDirectory
default:
e.ftype = fTypeFile
}
switch {
case e.e == 0:
e.e = syse
case syse != 0:
r.c <- &event{
pathw: e.pathw,
name: e.name,
ftype: e.ftype,
action: e.action,
filter: e.filter,
e: syse,
}
}
r.c <- e
}
}
// Rewatch implements notify.Rewatcher interface.
func (r *readdcw) Rewatch(path string, oldevent, newevent Event) error {
return r.rewatch(path, uint32(oldevent), uint32(newevent), false)
}
// RecursiveRewatch implements notify.RecursiveRewatcher interface.
func (r *readdcw) RecursiveRewatch(oldpath, newpath string, oldevent,
newevent Event) error {
if oldpath != newpath {
if err := r.unwatch(oldpath); err != nil {
return err
}
return r.watch(newpath, newevent, true)
}
return r.rewatch(newpath, uint32(oldevent), uint32(newevent), true)
}
// TODO : (pknap) doc.
func (r *readdcw) rewatch(path string, oldevent, newevent uint32, recursive bool) (err error) {
if Event(newevent)&^(All|fileNotifyChangeAll) != 0 {
return errors.New("notify: unknown event")
}
var wd *watched
r.Lock()
defer r.Unlock()
if wd, err = r.nonStateWatchedLocked(path); err != nil {
return
}
if wd.filter&(onlyNotifyChanges|onlyNGlobalEvents) != oldevent {
panic(`notify: windows re-watcher logic error`)
}
wd.filter = stateRewatch | newevent
wd.recursive, recursive = recursive, wd.recursive
if err = wd.closeHandle(); err != nil {
wd.filter = oldevent
wd.recursive = recursive
return
}
return
}
// TODO : pknap
func (r *readdcw) nonStateWatchedLocked(path string) (wd *watched, err error) {
wd, ok := r.m[path]
if !ok || wd == nil {
err = errors.New(`notify: ` + path + ` path is unwatched`)
return
}
if wd.filter&onlyMachineStates != 0 {
err = errors.New(`notify: another re/unwatching operation in progress`)
return
}
return
}
// Unwatch implements notify.Watcher interface.
func (r *readdcw) Unwatch(path string) error {
return r.unwatch(path)
}
// RecursiveUnwatch implements notify.RecursiveWatcher interface.
func (r *readdcw) RecursiveUnwatch(path string) error {
return r.unwatch(path)
}
// TODO : pknap
func (r *readdcw) unwatch(path string) (err error) {
var wd *watched
r.Lock()
defer r.Unlock()
if wd, err = r.nonStateWatchedLocked(path); err != nil {
return
}
wd.filter |= stateUnwatch
dbgprint("unwatch: set unwatch state")
if _, attrErr := syscall.GetFileAttributes(&wd.pathw[0]); attrErr != nil {
for _, g := range wd.digrip {
if g == nil {
continue
}
dbgprint("unwatch: posting")
if err = syscall.PostQueuedCompletionStatus(r.cph, 0, 0, (*syscall.Overlapped)(unsafe.Pointer(g.ovlapped))); err != nil {
wd.filter &^= stateUnwatch
return
}
}
}
return
}
// Close resets the whole watcher object, closes all existing file descriptors,
// and sends stateCPClose state as completion key to the main watcher's loop.
func (r *readdcw) Close() (err error) {
r.Lock()
if !r.start {
r.Unlock()
return nil
}
for _, wd := range r.m {
wd.filter &^= onlyMachineStates
wd.filter |= stateCPClose
if e := wd.closeHandle(); e != nil && err == nil {
err = e
}
}
r.start = false
r.Unlock()
r.wg.Add(1)
if e := syscall.PostQueuedCompletionStatus(r.cph, 0, stateCPClose, nil); e != nil && err == nil {
return e
}
r.wg.Wait()
return
}
// decode creates a notify event from both non-raw filter and action which was
// returned from completion routine. Function may return Event(0) in case when
// filter was replaced by a new value which does not contain fields that are
// valid with passed action.
func decode(filter, action uint32) (Event, Event) {
switch action {
case syscall.FILE_ACTION_ADDED:
return gensys(filter, Create, FileActionAdded)
case syscall.FILE_ACTION_REMOVED:
return gensys(filter, Remove, FileActionRemoved)
case syscall.FILE_ACTION_MODIFIED:
return gensys(filter, Write, FileActionModified)
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
return gensys(filter, Rename, FileActionRenamedOldName)
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
return gensys(filter, Rename, FileActionRenamedNewName)
}
panic(`notify: cannot decode internal mask`)
}
// gensys decides whether the Windows action, system-independent event or both
// of them should be returned. Since the grip's filter may be atomically changed
// during watcher lifetime, it is possible that neither Windows nor notify masks
// are watched by the user when this function is called.
func gensys(filter uint32, ge, se Event) (gene, syse Event) {
isdir := filter&uint32(dirmarker) != 0
if isdir && filter&uint32(FileNotifyChangeDirName) != 0 ||
!isdir && filter&uint32(FileNotifyChangeFileName) != 0 ||
filter&uint32(fileNotifyChangeModified) != 0 {
syse = se
}
if filter&uint32(ge) != 0 {
gene = ge
}
return
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build windows
package notify
import "testing"
// TODO(ppknap) : remove notify.Create event.
func rcreate(w *W, path string) WCase {
cas := create(w, path)
cas.Events = append(cas.Events,
&Call{P: path, E: FileActionAdded},
)
return cas
}
// TODO(ppknap) : remove notify.Remove event.
func rremove(w *W, path string) WCase {
cas := remove(w, path)
cas.Events = append(cas.Events,
&Call{P: path, E: FileActionRemoved},
)
return cas
}
// TODO(ppknap) : remove notify.Rename event.
func rrename(w *W, oldpath, newpath string) WCase {
cas := rename(w, oldpath, newpath)
cas.Events = append(cas.Events,
&Call{P: oldpath, E: FileActionRenamedOldName},
&Call{P: newpath, E: FileActionRenamedNewName},
)
return cas
}
// TODO(ppknap) : remove notify.Write event.
func rwrite(w *W, path string, p []byte) WCase {
cas := write(w, path, p)
cas.Events = append(cas.Events,
&Call{P: path, E: FileActionModified},
)
return cas
}
var events = []Event{
FileNotifyChangeFileName,
FileNotifyChangeDirName,
FileNotifyChangeSize,
}
func TestWatcherReadDirectoryChangesW(t *testing.T) {
w := NewWatcherTest(t, "testdata/vfs.txt", events...)
defer w.Close()
cases := [...]WCase{
rcreate(w, "src/github.com/rjeczalik/fs/fs_windows.go"),
rcreate(w, "src/github.com/rjeczalik/fs/subdir/"),
rremove(w, "src/github.com/rjeczalik/fs/fs.go"),
rrename(w, "src/github.com/rjeczalik/fs/LICENSE", "src/github.com/rjeczalik/fs/COPYLEFT"),
rwrite(w, "src/github.com/rjeczalik/fs/cmd/gotree/go.go", []byte("XD")),
}
w.ExpectAny(cases[:])
}
+102
View File
@@ -0,0 +1,102 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin,!kqueue windows
package notify
import (
"fmt"
"testing"
)
// noevent stripts test-case from expected event list, used when action is not
// expected to trigger any events.
func noevent(cas WCase) WCase {
return WCase{Action: cas.Action}
}
func TestWatcherRecursiveRewatch(t *testing.T) {
w := newWatcherTest(t, "testdata/vfs.txt")
defer w.Close()
cases := []WCase{
create(w, "src/github.com/rjeczalik/file"),
create(w, "src/github.com/rjeczalik/dir/"),
noevent(create(w, "src/github.com/rjeczalik/fs/dir/")),
noevent(create(w, "src/github.com/dir/")),
noevent(write(w, "src/github.com/rjeczalik/file", []byte("XD"))),
noevent(rename(w, "src/github.com/rjeczalik/fs/LICENSE", "src/LICENSE")),
}
w.Watch("src/github.com/rjeczalik", Create)
w.ExpectAny(cases)
cases = []WCase{
create(w, "src/github.com/rjeczalik/fs/file"),
create(w, "src/github.com/rjeczalik/fs/cmd/gotree/file"),
create(w, "src/github.com/rjeczalik/fs/cmd/dir/"),
create(w, "src/github.com/rjeczalik/fs/cmd/gotree/dir/"),
noevent(write(w, "src/github.com/rjeczalik/fs/file", []byte("XD"))),
noevent(create(w, "src/github.com/anotherdir/")),
}
w.RecursiveRewatch("src/github.com/rjeczalik", "src/github.com/rjeczalik", Create, Create)
w.ExpectAny(cases)
cases = []WCase{
create(w, "src/github.com/rjeczalik/1"),
create(w, "src/github.com/rjeczalik/2/"),
noevent(create(w, "src/github.com/rjeczalik/fs/cmd/1")),
noevent(create(w, "src/github.com/rjeczalik/fs/1/")),
noevent(write(w, "src/github.com/rjeczalik/fs/file", []byte("XD"))),
}
w.Rewatch("src/github.com/rjeczalik", Create, Create)
w.ExpectAny(cases)
}
// TODO(rjeczalik): move to watcher_test.go after #5
func TestIsDirCreateEvent(t *testing.T) {
w := NewWatcherTest(t, "testdata/vfs.txt")
defer w.Close()
cases := [...]WCase{
// i=0
create(w, "src/github.com/jszwec/"),
// i=1
create(w, "src/github.com/jszwec/gojunitxml/"),
// i=2
create(w, "src/github.com/jszwec/gojunitxml/README.md"),
// i=3
create(w, "src/github.com/jszwec/gojunitxml/LICENSE"),
// i=4
create(w, "src/github.com/jszwec/gojunitxml/cmd/"),
}
dirs := [...]bool{
true, // i=0
true, // i=1
false, // i=2
false, // i=3
true, // i=4
}
fn := func(i int, _ WCase, ei EventInfo) error {
d, ok := ei.(isDirer)
if !ok {
return fmt.Errorf("received EventInfo does not implement isDirer")
}
switch ok, err := d.isDir(); {
case err != nil:
return err
case ok != dirs[i]:
return fmt.Errorf("want ok=%v; got %v", dirs[i], ok)
default:
return nil
}
}
w.ExpectAnyFunc(cases[:], fn)
}
+13
View File
@@ -0,0 +1,13 @@
// Copyright (c) 2014-2018 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
type watcherStub struct{ error }
// Following methods implement notify.watcher interface.
func (s watcherStub) Watch(string, Event) error { return s }
func (s watcherStub) Rewatch(string, Event, Event) error { return s }
func (s watcherStub) Unwatch(string) (err error) { return s }
func (s watcherStub) Close() error { return s }
+86
View File
@@ -0,0 +1,86 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin linux freebsd dragonfly netbsd openbsd windows solaris
package notify
import (
"os"
"testing"
"time"
)
// NOTE Set NOTIFY_DEBUG env var or debug build tag for extra debugging info.
func TestWatcher(t *testing.T) {
w := NewWatcherTest(t, "testdata/vfs.txt")
defer w.Close()
cases := [...]WCase{
create(w, "src/github.com/ppknap/link/include/coost/.link.hpp.swp"),
create(w, "src/github.com/rjeczalik/fs/fs_test.go"),
create(w, "src/github.com/rjeczalik/fs/binfs/"),
create(w, "src/github.com/rjeczalik/fs/binfs.go"),
create(w, "src/github.com/rjeczalik/fs/binfs_test.go"),
remove(w, "src/github.com/rjeczalik/fs/binfs/"),
create(w, "src/github.com/rjeczalik/fs/binfs/"),
create(w, "src/github.com/rjeczalik/fs/virfs"),
remove(w, "src/github.com/rjeczalik/fs/virfs"),
create(w, "file"),
create(w, "dir/"),
}
w.ExpectAny(cases[:])
}
// Simulates the scenario, where outside of the programs control the base dir
// is removed. This is detected and the watch removed. Then the directory is
// restored and a new watch set up.
func TestStopPathNotExists(t *testing.T) {
w := NewWatcherTest(t, "testdata/vfs.txt")
defer w.Close()
if err := os.RemoveAll(w.root); err != nil {
panic(err)
}
Sync()
// Don't check the returned error, as the public function (notify.Stop)
// does not return a potential error. As long as everything later on
// works as inteded, that's fine
time.Sleep(time.Duration(100) * time.Millisecond)
w.Watcher.Unwatch(w.root)
time.Sleep(time.Duration(100) * time.Millisecond)
if err := os.Mkdir(w.root, 0777); err != nil {
panic(err)
}
Sync()
w.Watch("", All)
drainall(w.C)
cases := [...]WCase{
create(w, "file"),
create(w, "dir/"),
}
w.ExpectAny(cases[:])
}
func TestWatcherUnwatch(t *testing.T) {
w := NewWatcherTest(t, "testdata/vfs.txt")
defer w.Close()
remove(w, "src/github.com/ppknap/link/test/test_circular_calls.cpp").Action()
w.Unwatch("")
w.Watch("", All)
drainall(w.C)
cases := [...]WCase{
create(w, "file"),
create(w, "dir/"),
}
w.ExpectAny(cases[:])
}
+450
View File
@@ -0,0 +1,450 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin,kqueue dragonfly freebsd netbsd openbsd solaris
// watcher_trigger is used for FEN and kqueue which behave similarly:
// only files and dirs can be watched directly, but not files inside dirs.
// As a result Create events have to be generated by implementation when
// after Write event is returned for watched dir, it is rescanned and Create
// event is returned for new files and these are automatically added
// to watchlist. In case of removal of watched directory, native system returns
// events for all files, but for Rename, they also need to be generated.
// As a result native system works as something like trigger for rescan,
// but contains additional data about dir in which changes occurred. For files
// detailed data is returned.
// Usage of watcher_trigger requires:
// - trigger implementation,
// - encode func,
// - not2nat, nat2not maps.
// Required manual operations on filesystem can lead to loss of precision.
package notify
import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"syscall"
)
// trigger is to be implemented by platform implementation like FEN or kqueue.
type trigger interface {
// Close closes watcher's main native file descriptor.
Close() error
// Stop waiting for new events.
Stop() error
// Create new instance of watched.
NewWatched(string, os.FileInfo) (*watched, error)
// Record internally new *watched instance.
Record(*watched)
// Del removes internal copy of *watched instance.
Del(*watched)
// Watched returns *watched instance and native events for native type.
Watched(interface{}) (*watched, int64, error)
// Init initializes native watcher call.
Init() error
// Watch starts watching provided file/dir.
Watch(os.FileInfo, *watched, int64) error
// Unwatch stops watching provided file/dir.
Unwatch(*watched) error
// Wait for new events.
Wait() (interface{}, error)
// IsStop checks if Wait finished because of request watcher's stop.
IsStop(n interface{}, err error) bool
}
// trgWatched is a the base data structure representing watched file/directory.
// The platform specific full data structure (watched) must embed this type.
type trgWatched struct {
// p is a path to watched file/directory.
p string
// fi provides information about watched file/dir.
fi os.FileInfo
// eDir represents events watched directly.
eDir Event
// eNonDir represents events watched indirectly.
eNonDir Event
}
// encode Event to native representation. Implementation is to be provided by
// platform specific implementation.
var encode func(Event, bool) int64
var (
// nat2not matches native events to notify's ones. To be initialized by
// platform dependent implementation.
nat2not map[Event]Event
// not2nat matches notify's events to native ones. To be initialized by
// platform dependent implementation.
not2nat map[Event]Event
)
// trg is a main structure implementing watcher.
type trg struct {
sync.Mutex
// s is a channel used to stop monitoring.
s chan struct{}
// c is a channel used to pass events further.
c chan<- EventInfo
// pthLkp is a data structure mapping file names with data about watching
// represented by them files/directories.
pthLkp map[string]*watched
// t is a platform dependent implementation of trigger.
t trigger
}
// newWatcher returns new watcher's implementation.
func newWatcher(c chan<- EventInfo) watcher {
t := &trg{
s: make(chan struct{}, 1),
pthLkp: make(map[string]*watched, 0),
c: c,
}
t.t = newTrigger(t.pthLkp)
if err := t.t.Init(); err != nil {
t.Close()
return watcherStub{fmt.Errorf("failed setting up watcher: %v", err)}
}
go t.monitor()
return t
}
// Close implements watcher.
func (t *trg) Close() (err error) {
t.Lock()
if err = t.t.Stop(); err != nil {
t.Unlock()
return
}
<-t.s
var e error
for _, w := range t.pthLkp {
if e = t.unwatch(w.p, w.fi); e != nil {
dbgprintf("trg: unwatch %q failed: %q\n", w.p, e)
err = nonil(err, e)
}
}
if e = t.t.Close(); e != nil {
dbgprintf("trg: closing native watch failed: %q\n", e)
err = nonil(err, e)
}
if remaining := len(t.pthLkp); remaining != 0 {
err = nonil(err, fmt.Errorf("Not all watches were removed: len(t.pthLkp) == %v", len(t.pthLkp)))
}
t.Unlock()
return
}
// send reported events one by one through chan.
func (t *trg) send(evn []event) {
for i := range evn {
t.c <- &evn[i]
}
}
// singlewatch starts to watch given p file/directory.
func (t *trg) singlewatch(p string, e Event, direct mode, fi os.FileInfo) (err error) {
w, ok := t.pthLkp[p]
if !ok {
if w, err = t.t.NewWatched(p, fi); err != nil {
return
}
}
switch direct {
case dir:
w.eDir |= e
case ndir:
w.eNonDir |= e
case both:
w.eDir |= e
w.eNonDir |= e
}
if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir, fi.IsDir())); err != nil {
return
}
if !ok {
t.t.Record(w)
return nil
}
return errAlreadyWatched
}
// decode converts event received from native to notify.Event
// representation taking into account requested events (w).
func decode(o int64, w Event) (e Event) {
for f, n := range nat2not {
if o&int64(f) != 0 {
if w&f != 0 {
e |= f
}
if w&n != 0 {
e |= n
}
}
}
return
}
func (t *trg) watch(p string, e Event, fi os.FileInfo) error {
if err := t.singlewatch(p, e, dir, fi); err != nil {
if err != errAlreadyWatched {
return err
}
}
if fi.IsDir() {
err := t.walk(p, func(fi os.FileInfo) (err error) {
if err = t.singlewatch(filepath.Join(p, fi.Name()), e, ndir,
fi); err != nil {
if err != errAlreadyWatched {
return
}
}
return nil
})
if err != nil {
return err
}
}
return nil
}
// walk runs f func on each file/dir from p directory.
func (t *trg) walk(p string, fn func(os.FileInfo) error) error {
fp, err := os.Open(p)
if err != nil {
return err
}
ls, err := fp.Readdir(0)
fp.Close()
if err != nil {
return err
}
for i := range ls {
if err := fn(ls[i]); err != nil {
return err
}
}
return nil
}
func (t *trg) unwatch(p string, fi os.FileInfo) error {
if fi.IsDir() {
err := t.walk(p, func(fi os.FileInfo) error {
err := t.singleunwatch(filepath.Join(p, fi.Name()), ndir)
if err != errNotWatched {
return err
}
return nil
})
if err != nil {
return err
}
}
return t.singleunwatch(p, dir)
}
// Watch implements Watcher interface.
func (t *trg) Watch(p string, e Event) error {
fi, err := os.Stat(p)
if err != nil {
return err
}
t.Lock()
err = t.watch(p, e, fi)
t.Unlock()
return err
}
// Unwatch implements Watcher interface.
func (t *trg) Unwatch(p string) error {
fi, err := os.Stat(p)
if err != nil {
return err
}
t.Lock()
err = t.unwatch(p, fi)
t.Unlock()
return err
}
// Rewatch implements Watcher interface.
//
// TODO(rjeczalik): This is a naive hack. Rewrite might help.
func (t *trg) Rewatch(p string, _, e Event) error {
fi, err := os.Stat(p)
if err != nil {
return err
}
t.Lock()
if err = t.unwatch(p, fi); err == nil {
// TODO(rjeczalik): If watch fails then we leave trigger in inconsistent
// state. Handle? Panic? Native version of rewatch?
err = t.watch(p, e, fi)
}
t.Unlock()
return nil
}
func (*trg) file(w *watched, n interface{}, e Event) (evn []event) {
evn = append(evn, event{w.p, e, w.fi.IsDir(), n})
return
}
func (t *trg) dir(w *watched, n interface{}, e, ge Event) (evn []event) {
// If it's dir and delete we have to send it and continue, because
// other processing relies on opening (in this case not existing) dir.
// Events for contents of this dir are reported by native impl.
// However events for rename must be generated for all monitored files
// inside of moved directory, because native impl does not report it independently
// for each file descriptor being moved in result of move action on
// parent directory.
if (ge & (not2nat[Rename] | not2nat[Remove])) != 0 {
// Write is reported also for Remove on directory. Because of that
// we have to filter it out explicitly.
evn = append(evn, event{w.p, e & ^Write & ^not2nat[Write], true, n})
if ge&not2nat[Rename] != 0 {
for p := range t.pthLkp {
if strings.HasPrefix(p, w.p+string(os.PathSeparator)) {
if err := t.singleunwatch(p, both); err != nil && err != errNotWatched &&
!os.IsNotExist(err) {
dbgprintf("trg: failed stop watching moved file (%q): %q\n",
p, err)
}
if (w.eDir|w.eNonDir)&(not2nat[Rename]|Rename) != 0 {
evn = append(evn, event{
p, (w.eDir | w.eNonDir) & e &^ Write &^ not2nat[Write],
w.fi.IsDir(), nil,
})
}
}
}
}
t.t.Del(w)
return
}
if (ge & not2nat[Write]) != 0 {
switch err := t.walk(w.p, func(fi os.FileInfo) error {
p := filepath.Join(w.p, fi.Name())
switch err := t.singlewatch(p, w.eDir, ndir, fi); {
case os.IsNotExist(err) && ((w.eDir & Remove) != 0):
evn = append(evn, event{p, Remove, fi.IsDir(), n})
case err == errAlreadyWatched:
case err != nil:
dbgprintf("trg: watching %q failed: %q", p, err)
case (w.eDir & Create) != 0:
evn = append(evn, event{p, Create, fi.IsDir(), n})
default:
}
return nil
}); {
case os.IsNotExist(err):
return
case err != nil:
dbgprintf("trg: dir processing failed: %q", err)
default:
}
}
return
}
type mode uint
const (
dir mode = iota
ndir
both
)
// unwatch stops watching p file/directory.
func (t *trg) singleunwatch(p string, direct mode) error {
w, ok := t.pthLkp[p]
if !ok {
return errNotWatched
}
switch direct {
case dir:
w.eDir = 0
case ndir:
w.eNonDir = 0
case both:
w.eDir, w.eNonDir = 0, 0
}
if err := t.t.Unwatch(w); err != nil {
return err
}
if w.eNonDir|w.eDir != 0 {
mod := dir
if w.eNonDir != 0 {
mod = ndir
}
if err := t.singlewatch(p, w.eNonDir|w.eDir, mod,
w.fi); err != nil && err != errAlreadyWatched {
return err
}
} else {
t.t.Del(w)
}
return nil
}
func (t *trg) monitor() {
var (
n interface{}
err error
)
for {
switch n, err = t.t.Wait(); {
case err == syscall.EINTR:
case t.t.IsStop(n, err):
t.s <- struct{}{}
return
case err != nil:
dbgprintf("trg: failed to read events: %q\n", err)
default:
t.send(t.process(n))
}
}
}
// process event returned by native call.
func (t *trg) process(n interface{}) (evn []event) {
t.Lock()
w, ge, err := t.t.Watched(n)
if err != nil {
t.Unlock()
dbgprintf("trg: %v event lookup failed: %q", Event(ge), err)
return
}
e := decode(ge, w.eDir|w.eNonDir)
if ge&int64(not2nat[Remove]|not2nat[Rename]) == 0 {
switch fi, err := os.Stat(w.p); {
case err != nil:
default:
if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir, fi.IsDir())); err != nil {
dbgprintf("trg: %q is no longer watched: %q", w.p, err)
t.t.Del(w)
}
}
}
if e == Event(0) && (!w.fi.IsDir() || (ge&int64(not2nat[Write])) == 0) {
t.Unlock()
return
}
if w.fi.IsDir() {
evn = append(evn, t.dir(w, n, e, Event(ge))...)
} else {
evn = append(evn, t.file(w, n, e)...)
}
if Event(ge)&(not2nat[Remove]|not2nat[Rename]) != 0 {
t.t.Del(w)
}
t.Unlock()
return
}
+21
View File
@@ -0,0 +1,21 @@
// Copyright (c) 2014-2017 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build darwin,kqueue dragonfly freebsd netbsd openbsd solaris
package notify
import "testing"
func TestWatcherCreateOnly(t *testing.T) {
w := NewWatcherTest(t, "testdata/vfs.txt", Create)
defer w.Close()
cases := [...]WCase{
create(w, "dir/"),
create(w, "dir2/"),
}
w.ExpectAny(cases[:])
}
+103
View File
@@ -0,0 +1,103 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
// EventDiff describes a change to an event set - EventDiff[0] is an old state,
// while EventDiff[1] is a new state. If event set has not changed (old == new),
// functions typically return the None value.
type eventDiff [2]Event
func (diff eventDiff) Event() Event {
return diff[1] &^ diff[0]
}
// Watchpoint
//
// The nil key holds total event set - logical sum for all registered events.
// It speeds up computing EventDiff for Add method.
//
// The rec key holds an event set for a watchpoints created by RecursiveWatch
// for a Watcher implementation which is not natively recursive.
type watchpoint map[chan<- EventInfo]Event
// None is an empty event diff, think null object.
var none eventDiff
// rec is just a placeholder
var rec = func() (ch chan<- EventInfo) {
ch = make(chan<- EventInfo)
close(ch)
return
}()
func (wp watchpoint) dryAdd(ch chan<- EventInfo, e Event) eventDiff {
if e &^= internal; wp[ch]&e == e {
return none
}
total := wp[ch] &^ internal
return eventDiff{total, total | e}
}
// Add assumes neither c nor e are nil or zero values.
func (wp watchpoint) Add(c chan<- EventInfo, e Event) (diff eventDiff) {
wp[c] |= e
diff[0] = wp[nil]
diff[1] = diff[0] | e
wp[nil] = diff[1] &^ omit
// Strip diff from internal events.
diff[0] &^= internal
diff[1] &^= internal
if diff[0] == diff[1] {
return none
}
return
}
func (wp watchpoint) Del(c chan<- EventInfo, e Event) (diff eventDiff) {
wp[c] &^= e
if wp[c] == 0 {
delete(wp, c)
}
diff[0] = wp[nil]
delete(wp, nil)
if len(wp) != 0 {
// Recalculate total event set.
for _, e := range wp {
diff[1] |= e
}
wp[nil] = diff[1] &^ omit
}
// Strip diff from internal events.
diff[0] &^= internal
diff[1] &^= internal
if diff[0] == diff[1] {
return none
}
return
}
func (wp watchpoint) Dispatch(ei EventInfo, extra Event) {
e := eventmask(ei, extra)
if !matches(wp[nil], e) {
return
}
for ch, eset := range wp {
if ch != nil && matches(eset, e) {
select {
case ch <- ei:
default: // Drop event if receiver is too slow
dbgprintf("dropped %s on %q: receiver too slow", ei.Event(), ei.Path())
}
}
}
}
func (wp watchpoint) Total() Event {
return wp[nil] &^ internal
}
func (wp watchpoint) IsRecursive() bool {
return wp[nil]&recursive != 0
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build !windows
package notify
// eventmask uses ei to create a new event which contains internal flags used by
// notify package logic.
func eventmask(ei EventInfo, extra Event) Event {
return ei.Event() | extra
}
// matches reports a match only when:
//
// - for user events, when event is present in the given set
// - for internal events, when additionally both event and set have omit bit set
//
// Internal events must not be sent to user channels and vice versa.
func matches(set, event Event) bool {
return (set&omit)^(event&omit) == 0 && set&event == event
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
// +build windows
package notify
// eventmask uses ei to create a new event which contains internal flags used by
// notify package logic. If one of FileAction* masks is detected, this function
// adds corresponding FileNotifyChange* values. This allows non registered
// FileAction* events to be passed on.
func eventmask(ei EventInfo, extra Event) (e Event) {
if e = ei.Event() | extra; e&fileActionAll != 0 {
if ev, ok := ei.(*event); ok {
switch ev.ftype {
case fTypeFile:
e |= FileNotifyChangeFileName
case fTypeDirectory:
e |= FileNotifyChangeDirName
case fTypeUnknown:
e |= fileNotifyChangeModified
}
return e &^ fileActionAll
}
}
return
}
// matches reports a match only when:
//
// - for user events, when event is present in the given set
// - for internal events, when additionally both event and set have omit bit set
//
// Internal events must not be sent to user channels and vice versa.
func matches(set, event Event) bool {
return (set&omit)^(event&omit) == 0 && (set&event == event || set&fileNotifyChangeModified&event != 0)
}
+162
View File
@@ -0,0 +1,162 @@
// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
package notify
import (
"fmt"
"reflect"
"testing"
)
func call(wp watchpoint, fn interface{}, args []interface{}) eventDiff {
vals := []reflect.Value{reflect.ValueOf(wp)}
for _, arg := range args {
vals = append(vals, reflect.ValueOf(arg))
}
res := reflect.ValueOf(fn).Call(vals)
if n := len(res); n != 1 {
panic(fmt.Sprintf("unexpected len(res)=%d", n))
}
diff, ok := res[0].Interface().(eventDiff)
if !ok {
panic(fmt.Sprintf("want typeof(diff)=EventDiff; got %T", res[0].Interface()))
}
return diff
}
func TestWatchpoint(t *testing.T) {
ch := NewChans(5)
all := All | recursive
cases := [...]struct {
fn interface{}
args []interface{}
diff eventDiff
total Event
}{
// i=0
{
watchpoint.Add,
[]interface{}{ch[0], Remove},
eventDiff{0, Remove},
Remove,
},
// i=1
{
watchpoint.Add,
[]interface{}{ch[1], Create | Remove | recursive},
eventDiff{Remove, Remove | Create},
Create | Remove | recursive,
},
// i=2
{
watchpoint.Add,
[]interface{}{ch[2], Create | Rename},
eventDiff{Create | Remove, Create | Remove | Rename},
Create | Remove | Rename | recursive,
},
// i=3
{
watchpoint.Add,
[]interface{}{ch[0], Write | recursive},
eventDiff{Create | Remove | Rename, Create | Remove | Rename | Write},
Create | Remove | Rename | Write | recursive,
},
// i=4
{
watchpoint.Add,
[]interface{}{ch[2], Remove | recursive},
none,
Create | Remove | Rename | Write | recursive,
},
// i=5
{
watchpoint.Del,
[]interface{}{ch[0], all},
eventDiff{Create | Remove | Rename | Write, Create | Remove | Rename},
Create | Remove | Rename | recursive,
},
// i=6
{
watchpoint.Del,
[]interface{}{ch[2], all},
eventDiff{Create | Remove | Rename, Create | Remove},
Create | Remove | recursive,
},
// i=7
{
watchpoint.Add,
[]interface{}{ch[3], Create | Remove},
none,
Create | Remove | recursive,
},
// i=8
{
watchpoint.Del,
[]interface{}{ch[1], all},
none,
Create | Remove,
},
// i=9
{
watchpoint.Add,
[]interface{}{ch[3], recursive | Write},
eventDiff{Create | Remove, Create | Remove | Write},
Create | Remove | Write | recursive,
},
// i=10
{
watchpoint.Del,
[]interface{}{ch[3], Create},
eventDiff{Create | Remove | Write, Remove | Write},
Remove | Write | recursive,
},
// i=11
{
watchpoint.Add,
[]interface{}{ch[3], Create | Rename},
eventDiff{Remove | Write, Create | Remove | Rename | Write},
Create | Remove | Rename | Write | recursive,
},
// i=12
{
watchpoint.Add,
[]interface{}{ch[2], Remove | Write},
none,
Create | Remove | Rename | Write | recursive,
},
// i=13
{
watchpoint.Del,
[]interface{}{ch[3], Create | Remove | Write},
eventDiff{Create | Remove | Rename | Write, Remove | Rename | Write},
Remove | Rename | Write | recursive,
},
// i=14
{
watchpoint.Del,
[]interface{}{ch[2], Remove},
eventDiff{Remove | Rename | Write, Rename | Write},
Rename | Write | recursive,
},
// i=15
{
watchpoint.Del,
[]interface{}{ch[3], Rename | recursive},
eventDiff{Rename | Write, Write},
Write,
},
}
wp := watchpoint{}
for i, cas := range cases {
if diff := call(wp, cas.fn, cas.args); diff != cas.diff {
t.Errorf("want diff=%v; got %v (i=%d)", cas.diff, diff, i)
continue
}
if total := wp[nil]; total != cas.total {
t.Errorf("want total=%v; got %v (i=%d)", cas.total, total, i)
continue
}
}
}
+10
View File
@@ -0,0 +1,10 @@
# Treat all files in this repo as binary, with no git magic updating
# line endings. Windows users contributing to Go will need to use a
# modern version of git and editors capable of LF line endings.
#
# We'll prevent accidental CRLF line endings from entering the repo
# via the git-review gofmt checks.
#
# See golang.org/issue/9281
* -text
+2
View File
@@ -0,0 +1,2 @@
# Add no patterns to .gitignore except for files generated by the build.
last-change
+3
View File
@@ -0,0 +1,3 @@
# This source code refers to The Go Authors for copyright purposes.
# The master list of authors is in the main Go distribution,
# visible at http://tip.golang.org/AUTHORS.
+26
View File
@@ -0,0 +1,26 @@
# Contributing to Go
Go is an open source project.
It is the work of hundreds of contributors. We appreciate your help!
## Filing issues
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
1. What version of Go are you using (`go version`)?
2. What operating system and processor architecture are you using?
3. What did you do?
4. What did you expect to see?
5. What did you see instead?
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
## Contributing code
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
before sending patches.
Unless otherwise noted, the Go source files are distributed under
the BSD-style license found in the LICENSE file.
+3
View File
@@ -0,0 +1,3 @@
# This source code was written by the Go contributors.
# The master list of contributors is in the main Go distribution,
# visible at http://tip.golang.org/CONTRIBUTORS.
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+22
View File
@@ -0,0 +1,22 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.
+27
View File
@@ -0,0 +1,27 @@
# Go Tools
This subrepository holds the source for various packages and tools that support
the Go programming language.
Some of the tools, `godoc` and `vet` for example, are included in binary Go
distributions.
Others, including the Go `guru` and the test coverage tool, can be fetched with
`go get`.
Packages include a type-checker for Go and an implementation of the
Static Single Assignment form (SSA) representation for Go programs.
## Download/Install
The easiest way to install is to run `go get -u golang.org/x/tools/...`. You can
also manually git clone the repository to `$GOPATH/src/golang.org/x/tools`.
## Report Issues / Send Patches
This repository uses Gerrit for code changes. To learn how to submit changes to
this repository, see https://golang.org/doc/contribute.html.
The main issue tracker for the tools repository is located at
https://github.com/golang/go/issues. Prefix your issue with "x/tools/(your
subdir):" in the subject line, so it is easy to find.
+131
View File
@@ -0,0 +1,131 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package parse provides support for parsing benchmark results as
// generated by 'go test -bench'.
package parse // import "golang.org/x/tools/benchmark/parse"
import (
"bufio"
"bytes"
"fmt"
"io"
"strconv"
"strings"
)
// Flags used by Benchmark.Measured to indicate
// which measurements a Benchmark contains.
const (
NsPerOp = 1 << iota
MBPerS
AllocedBytesPerOp
AllocsPerOp
)
// Benchmark is one run of a single benchmark.
type Benchmark struct {
Name string // benchmark name
N int // number of iterations
NsPerOp float64 // nanoseconds per iteration
AllocedBytesPerOp uint64 // bytes allocated per iteration
AllocsPerOp uint64 // allocs per iteration
MBPerS float64 // MB processed per second
Measured int // which measurements were recorded
Ord int // ordinal position within a benchmark run
}
// ParseLine extracts a Benchmark from a single line of testing.B
// output.
func ParseLine(line string) (*Benchmark, error) {
fields := strings.Fields(line)
// Two required, positional fields: Name and iterations.
if len(fields) < 2 {
return nil, fmt.Errorf("two fields required, have %d", len(fields))
}
if !strings.HasPrefix(fields[0], "Benchmark") {
return nil, fmt.Errorf(`first field does not start with "Benchmark"`)
}
n, err := strconv.Atoi(fields[1])
if err != nil {
return nil, err
}
b := &Benchmark{Name: fields[0], N: n}
// Parse any remaining pairs of fields; we've parsed one pair already.
for i := 1; i < len(fields)/2; i++ {
b.parseMeasurement(fields[i*2], fields[i*2+1])
}
return b, nil
}
func (b *Benchmark) parseMeasurement(quant string, unit string) {
switch unit {
case "ns/op":
if f, err := strconv.ParseFloat(quant, 64); err == nil {
b.NsPerOp = f
b.Measured |= NsPerOp
}
case "MB/s":
if f, err := strconv.ParseFloat(quant, 64); err == nil {
b.MBPerS = f
b.Measured |= MBPerS
}
case "B/op":
if i, err := strconv.ParseUint(quant, 10, 64); err == nil {
b.AllocedBytesPerOp = i
b.Measured |= AllocedBytesPerOp
}
case "allocs/op":
if i, err := strconv.ParseUint(quant, 10, 64); err == nil {
b.AllocsPerOp = i
b.Measured |= AllocsPerOp
}
}
}
func (b *Benchmark) String() string {
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "%s %d", b.Name, b.N)
if (b.Measured & NsPerOp) != 0 {
fmt.Fprintf(buf, " %.2f ns/op", b.NsPerOp)
}
if (b.Measured & MBPerS) != 0 {
fmt.Fprintf(buf, " %.2f MB/s", b.MBPerS)
}
if (b.Measured & AllocedBytesPerOp) != 0 {
fmt.Fprintf(buf, " %d B/op", b.AllocedBytesPerOp)
}
if (b.Measured & AllocsPerOp) != 0 {
fmt.Fprintf(buf, " %d allocs/op", b.AllocsPerOp)
}
return buf.String()
}
// Set is a collection of benchmarks from one
// testing.B run, keyed by name to facilitate comparison.
type Set map[string][]*Benchmark
// ParseSet extracts a Set from testing.B output.
// ParseSet preserves the order of benchmarks that have identical
// names.
func ParseSet(r io.Reader) (Set, error) {
bb := make(Set)
scan := bufio.NewScanner(r)
ord := 0
for scan.Scan() {
if b, err := ParseLine(scan.Text()); err == nil {
b.Ord = ord
ord++
bb[b.Name] = append(bb[b.Name], b)
}
}
if err := scan.Err(); err != nil {
return nil, err
}
return bb, nil
}
+154
View File
@@ -0,0 +1,154 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package parse
import (
"reflect"
"strings"
"testing"
)
func TestParseLine(t *testing.T) {
cases := []struct {
line string
want *Benchmark
err bool // expect an error
}{
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6,
Measured: NsPerOp,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77 MB/s",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6, MBPerS: 817.77,
Measured: NsPerOp | MBPerS,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6,
Measured: NsPerOp,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77 MB/s 5 allocs/op",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6, MBPerS: 817.77, AllocsPerOp: 5,
Measured: NsPerOp | MBPerS | AllocsPerOp,
},
},
{
line: "BenchmarkEncrypt 100000000 19.6 ns/op 817.77 MB/s 3 B/op 5 allocs/op",
want: &Benchmark{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6, MBPerS: 817.77, AllocedBytesPerOp: 3, AllocsPerOp: 5,
Measured: NsPerOp | MBPerS | AllocedBytesPerOp | AllocsPerOp,
},
},
// error handling cases
{
line: "BenchPress 100 19.6 ns/op", // non-benchmark
err: true,
},
{
line: "BenchmarkEncrypt lots 19.6 ns/op", // non-int iterations
err: true,
},
{
line: "BenchmarkBridge 100000000 19.6 smoots", // unknown unit
want: &Benchmark{
Name: "BenchmarkBridge",
N: 100000000,
},
},
{
line: "PASS",
err: true,
},
}
for _, tt := range cases {
have, err := ParseLine(tt.line)
if tt.err && err == nil {
t.Errorf("parsing line %q should have failed", tt.line)
continue
}
if !reflect.DeepEqual(have, tt.want) {
t.Errorf("parsed line %q incorrectly, want %v have %v", tt.line, tt.want, have)
}
}
}
func TestParseSet(t *testing.T) {
// Test two things:
// 1. The noise that can accompany testing.B output gets ignored.
// 2. Benchmarks with the same name have their order preserved.
in := `
? crypto [no test files]
PASS
pem_decrypt_test.go:17: test 4. %!s(x509.PEMCipher=5)
... [output truncated]
BenchmarkEncrypt 100000000 19.6 ns/op
BenchmarkEncrypt 5000000 517 ns/op
=== RUN TestChunk
--- PASS: TestChunk (0.00 seconds)
--- SKIP: TestLinuxSendfile (0.00 seconds)
fs_test.go:716: skipping; linux-only test
BenchmarkReadRequestApachebench 1000000 2960 ns/op 27.70 MB/s 839 B/op 9 allocs/op
BenchmarkClientServerParallel64 50000 59192 ns/op 7028 B/op 60 allocs/op
ok net/http 95.783s
`
want := Set{
"BenchmarkReadRequestApachebench": []*Benchmark{
{
Name: "BenchmarkReadRequestApachebench",
N: 1000000, NsPerOp: 2960, MBPerS: 27.70, AllocedBytesPerOp: 839, AllocsPerOp: 9,
Measured: NsPerOp | MBPerS | AllocedBytesPerOp | AllocsPerOp,
Ord: 2,
},
},
"BenchmarkClientServerParallel64": []*Benchmark{
{
Name: "BenchmarkClientServerParallel64",
N: 50000, NsPerOp: 59192, AllocedBytesPerOp: 7028, AllocsPerOp: 60,
Measured: NsPerOp | AllocedBytesPerOp | AllocsPerOp,
Ord: 3,
},
},
"BenchmarkEncrypt": []*Benchmark{
{
Name: "BenchmarkEncrypt",
N: 100000000, NsPerOp: 19.6,
Measured: NsPerOp,
Ord: 0,
},
{
Name: "BenchmarkEncrypt",
N: 5000000, NsPerOp: 517,
Measured: NsPerOp,
Ord: 1,
},
},
}
have, err := ParseSet(strings.NewReader(in))
if err != nil {
t.Fatalf("unexpected err during ParseSet: %v", err)
}
if !reflect.DeepEqual(want, have) {
t.Errorf("parsed bench set incorrectly, want %v have %v", want, have)
}
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Adapted from encoding/xml/read_test.go.
// Package atom defines XML data structures for an Atom feed.
package atom // import "golang.org/x/tools/blog/atom"
import (
"encoding/xml"
"time"
)
type Feed struct {
XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"`
Title string `xml:"title"`
ID string `xml:"id"`
Link []Link `xml:"link"`
Updated TimeStr `xml:"updated"`
Author *Person `xml:"author"`
Entry []*Entry `xml:"entry"`
}
type Entry struct {
Title string `xml:"title"`
ID string `xml:"id"`
Link []Link `xml:"link"`
Published TimeStr `xml:"published"`
Updated TimeStr `xml:"updated"`
Author *Person `xml:"author"`
Summary *Text `xml:"summary"`
Content *Text `xml:"content"`
}
type Link struct {
Rel string `xml:"rel,attr,omitempty"`
Href string `xml:"href,attr"`
Type string `xml:"type,attr,omitempty"`
HrefLang string `xml:"hreflang,attr,omitempty"`
Title string `xml:"title,attr,omitempty"`
Length uint `xml:"length,attr,omitempty"`
}
type Person struct {
Name string `xml:"name"`
URI string `xml:"uri,omitempty"`
Email string `xml:"email,omitempty"`
InnerXML string `xml:",innerxml"`
}
type Text struct {
Type string `xml:"type,attr"`
Body string `xml:",chardata"`
}
type TimeStr string
func Time(t time.Time) TimeStr {
return TimeStr(t.Format("2006-01-02T15:04:05-07:00"))
}
+456
View File
@@ -0,0 +1,456 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package blog implements a web server for articles written in present format.
package blog // import "golang.org/x/tools/blog"
import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"html/template"
"log"
"net/http"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
"golang.org/x/tools/blog/atom"
"golang.org/x/tools/present"
)
var (
validJSONPFunc = regexp.MustCompile(`(?i)^[a-z_][a-z0-9_.]*$`)
// used to serve relative paths when ServeLocalLinks is enabled.
golangOrgAbsLinkReplacer = strings.NewReplacer(
`href="https://golang.org/pkg`, `href="/pkg`,
`href="https://golang.org/cmd`, `href="/cmd`,
)
)
// Config specifies Server configuration values.
type Config struct {
ContentPath string // Relative or absolute location of article files and related content.
TemplatePath string // Relative or absolute location of template files.
BaseURL string // Absolute base URL (for permalinks; no trailing slash).
BasePath string // Base URL path relative to server root (no trailing slash).
GodocURL string // The base URL of godoc (for menu bar; no trailing slash).
Hostname string // Server host name, used for rendering ATOM feeds.
HomeArticles int // Articles to display on the home page.
FeedArticles int // Articles to include in Atom and JSON feeds.
FeedTitle string // The title of the Atom XML feed
PlayEnabled bool
ServeLocalLinks bool // rewrite golang.org/{pkg,cmd} links to host-less, relative paths.
}
// Doc represents an article adorned with presentation data.
type Doc struct {
*present.Doc
Permalink string // Canonical URL for this document.
Path string // Path relative to server root (including base).
HTML template.HTML // rendered article
Related []*Doc
Newer, Older *Doc
}
// Server implements an http.Handler that serves blog articles.
type Server struct {
cfg Config
docs []*Doc
tags []string
docPaths map[string]*Doc // key is path without BasePath.
docTags map[string][]*Doc
template struct {
home, index, article, doc *template.Template
}
atomFeed []byte // pre-rendered Atom feed
jsonFeed []byte // pre-rendered JSON feed
content http.Handler
}
// NewServer constructs a new Server using the specified config.
func NewServer(cfg Config) (*Server, error) {
present.PlayEnabled = cfg.PlayEnabled
if notExist(cfg.TemplatePath) {
return nil, fmt.Errorf("template directory not found: %s", cfg.TemplatePath)
}
root := filepath.Join(cfg.TemplatePath, "root.tmpl")
parse := func(name string) (*template.Template, error) {
path := filepath.Join(cfg.TemplatePath, name)
if notExist(path) {
return nil, fmt.Errorf("template %s was not found in %s", name, cfg.TemplatePath)
}
t := template.New("").Funcs(funcMap)
return t.ParseFiles(root, path)
}
s := &Server{cfg: cfg}
// Parse templates.
var err error
s.template.home, err = parse("home.tmpl")
if err != nil {
return nil, err
}
s.template.index, err = parse("index.tmpl")
if err != nil {
return nil, err
}
s.template.article, err = parse("article.tmpl")
if err != nil {
return nil, err
}
p := present.Template().Funcs(funcMap)
s.template.doc, err = p.ParseFiles(filepath.Join(cfg.TemplatePath, "doc.tmpl"))
if err != nil {
return nil, err
}
// Load content.
err = s.loadDocs(filepath.Clean(cfg.ContentPath))
if err != nil {
return nil, err
}
err = s.renderAtomFeed()
if err != nil {
return nil, err
}
err = s.renderJSONFeed()
if err != nil {
return nil, err
}
// Set up content file server.
s.content = http.StripPrefix(s.cfg.BasePath, http.FileServer(http.Dir(cfg.ContentPath)))
return s, nil
}
var funcMap = template.FuncMap{
"sectioned": sectioned,
"authors": authors,
}
// sectioned returns true if the provided Doc contains more than one section.
// This is used to control whether to display the table of contents and headings.
func sectioned(d *present.Doc) bool {
return len(d.Sections) > 1
}
// authors returns a comma-separated list of author names.
func authors(authors []present.Author) string {
var b bytes.Buffer
last := len(authors) - 1
for i, a := range authors {
if i > 0 {
if i == last {
b.WriteString(" and ")
} else {
b.WriteString(", ")
}
}
b.WriteString(authorName(a))
}
return b.String()
}
// authorName returns the first line of the Author text: the author's name.
func authorName(a present.Author) string {
el := a.TextElem()
if len(el) == 0 {
return ""
}
text, ok := el[0].(present.Text)
if !ok || len(text.Lines) == 0 {
return ""
}
return text.Lines[0]
}
// loadDocs reads all content from the provided file system root, renders all
// the articles it finds, adds them to the Server's docs field, computes the
// denormalized docPaths, docTags, and tags fields, and populates the various
// helper fields (Next, Previous, Related) for each Doc.
func (s *Server) loadDocs(root string) error {
// Read content into docs field.
const ext = ".article"
fn := func(p string, info os.FileInfo, err error) error {
if filepath.Ext(p) != ext {
return nil
}
f, err := os.Open(p)
if err != nil {
return err
}
defer f.Close()
d, err := present.Parse(f, p, 0)
if err != nil {
return err
}
var html bytes.Buffer
err = d.Render(&html, s.template.doc)
if err != nil {
return err
}
p = p[len(root) : len(p)-len(ext)] // trim root and extension
p = filepath.ToSlash(p)
s.docs = append(s.docs, &Doc{
Doc: d,
Path: s.cfg.BasePath + p,
Permalink: s.cfg.BaseURL + p,
HTML: template.HTML(html.String()),
})
return nil
}
err := filepath.Walk(root, fn)
if err != nil {
return err
}
sort.Sort(docsByTime(s.docs))
// Pull out doc paths and tags and put in reverse-associating maps.
s.docPaths = make(map[string]*Doc)
s.docTags = make(map[string][]*Doc)
for _, d := range s.docs {
s.docPaths[strings.TrimPrefix(d.Path, s.cfg.BasePath)] = d
for _, t := range d.Tags {
s.docTags[t] = append(s.docTags[t], d)
}
}
// Pull out unique sorted list of tags.
for t := range s.docTags {
s.tags = append(s.tags, t)
}
sort.Strings(s.tags)
// Set up presentation-related fields, Newer, Older, and Related.
for _, doc := range s.docs {
// Newer, Older: docs adjacent to doc
for i := range s.docs {
if s.docs[i] != doc {
continue
}
if i > 0 {
doc.Newer = s.docs[i-1]
}
if i+1 < len(s.docs) {
doc.Older = s.docs[i+1]
}
break
}
// Related: all docs that share tags with doc.
related := make(map[*Doc]bool)
for _, t := range doc.Tags {
for _, d := range s.docTags[t] {
if d != doc {
related[d] = true
}
}
}
for d := range related {
doc.Related = append(doc.Related, d)
}
sort.Sort(docsByTime(doc.Related))
}
return nil
}
// renderAtomFeed generates an XML Atom feed and stores it in the Server's
// atomFeed field.
func (s *Server) renderAtomFeed() error {
var updated time.Time
if len(s.docs) > 0 {
updated = s.docs[0].Time
}
feed := atom.Feed{
Title: s.cfg.FeedTitle,
ID: "tag:" + s.cfg.Hostname + ",2013:" + s.cfg.Hostname,
Updated: atom.Time(updated),
Link: []atom.Link{{
Rel: "self",
Href: s.cfg.BaseURL + "/feed.atom",
}},
}
for i, doc := range s.docs {
if i >= s.cfg.FeedArticles {
break
}
e := &atom.Entry{
Title: doc.Title,
ID: feed.ID + doc.Path,
Link: []atom.Link{{
Rel: "alternate",
Href: doc.Permalink,
}},
Published: atom.Time(doc.Time),
Updated: atom.Time(doc.Time),
Summary: &atom.Text{
Type: "html",
Body: summary(doc),
},
Content: &atom.Text{
Type: "html",
Body: string(doc.HTML),
},
Author: &atom.Person{
Name: authors(doc.Authors),
},
}
feed.Entry = append(feed.Entry, e)
}
data, err := xml.Marshal(&feed)
if err != nil {
return err
}
s.atomFeed = data
return nil
}
type jsonItem struct {
Title string
Link string
Time time.Time
Summary string
Content string
Author string
}
// renderJSONFeed generates a JSON feed and stores it in the Server's jsonFeed
// field.
func (s *Server) renderJSONFeed() error {
var feed []jsonItem
for i, doc := range s.docs {
if i >= s.cfg.FeedArticles {
break
}
item := jsonItem{
Title: doc.Title,
Link: doc.Permalink,
Time: doc.Time,
Summary: summary(doc),
Content: string(doc.HTML),
Author: authors(doc.Authors),
}
feed = append(feed, item)
}
data, err := json.Marshal(feed)
if err != nil {
return err
}
s.jsonFeed = data
return nil
}
// summary returns the first paragraph of text from the provided Doc.
func summary(d *Doc) string {
if len(d.Sections) == 0 {
return ""
}
for _, elem := range d.Sections[0].Elem {
text, ok := elem.(present.Text)
if !ok || text.Pre {
// skip everything but non-text elements
continue
}
var buf bytes.Buffer
for _, s := range text.Lines {
buf.WriteString(string(present.Style(s)))
buf.WriteByte('\n')
}
return buf.String()
}
return ""
}
// rootData encapsulates data destined for the root template.
type rootData struct {
Doc *Doc
BasePath string
GodocURL string
Data interface{}
}
// ServeHTTP serves the front, index, and article pages
// as well as the ATOM and JSON feeds.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var (
d = rootData{BasePath: s.cfg.BasePath, GodocURL: s.cfg.GodocURL}
t *template.Template
)
switch p := strings.TrimPrefix(r.URL.Path, s.cfg.BasePath); p {
case "/":
d.Data = s.docs
if len(s.docs) > s.cfg.HomeArticles {
d.Data = s.docs[:s.cfg.HomeArticles]
}
t = s.template.home
case "/index":
d.Data = s.docs
t = s.template.index
case "/feed.atom", "/feeds/posts/default":
w.Header().Set("Content-type", "application/atom+xml; charset=utf-8")
w.Write(s.atomFeed)
return
case "/.json":
if p := r.FormValue("jsonp"); validJSONPFunc.MatchString(p) {
w.Header().Set("Content-type", "application/javascript; charset=utf-8")
fmt.Fprintf(w, "%v(%s)", p, s.jsonFeed)
return
}
w.Header().Set("Content-type", "application/json; charset=utf-8")
w.Write(s.jsonFeed)
return
default:
doc, ok := s.docPaths[p]
if !ok {
// Not a doc; try to just serve static content.
s.content.ServeHTTP(w, r)
return
}
d.Doc = doc
t = s.template.article
}
var err error
if s.cfg.ServeLocalLinks {
var buf bytes.Buffer
err = t.ExecuteTemplate(&buf, "root", d)
if err != nil {
log.Println(err)
return
}
_, err = golangOrgAbsLinkReplacer.WriteString(w, buf.String())
} else {
err = t.ExecuteTemplate(w, "root", d)
}
if err != nil {
log.Println(err)
}
}
// docsByTime implements sort.Interface, sorting Docs by their Time field.
type docsByTime []*Doc
func (s docsByTime) Len() int { return len(s) }
func (s docsByTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s docsByTime) Less(i, j int) bool { return s[i].Time.After(s[j].Time) }
// notExist reports whether the path exists or not.
func notExist(path string) bool {
_, err := os.Stat(path)
return os.IsNotExist(err)
}
+44
View File
@@ -0,0 +1,44 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package blog
import (
"bytes"
"testing"
)
func TestLinkRewrite(t *testing.T) {
tests := []struct {
input string
output string
}{
{
`For instance, the <a href="https://golang.org/pkg/bytes/" target="_blank">bytes package</a> from the standard library exports the <code>Buffer</code> type.`,
`For instance, the <a href="/pkg/bytes/" target="_blank">bytes package</a> from the standard library exports the <code>Buffer</code> type.`},
{
`(The <a href="https://golang.org/cmd/gofmt/" target="_blank">gofmt command</a> has a <code>-r</code> flag that provides a syntax-aware search and replace, making large-scale refactoring easier.)`,
`(The <a href="/cmd/gofmt/" target="_blank">gofmt command</a> has a <code>-r</code> flag that provides a syntax-aware search and replace, making large-scale refactoring easier.)`,
},
{
`<a href="//golang.org/LICENSE">BSD license</a>.<br> <a href="//golang.org/doc/tos.html">Terms of Service</a> `,
`<a href="//golang.org/LICENSE">BSD license</a>.<br> <a href="//golang.org/doc/tos.html">Terms of Service</a> `,
},
{
`For instance, the <code>websocket</code> package from the <code>go.net</code> sub-repository has an import path of <code>&#34;golang.org/x/net/websocket&#34;</code>.`,
`For instance, the <code>websocket</code> package from the <code>go.net</code> sub-repository has an import path of <code>&#34;golang.org/x/net/websocket&#34;</code>.`,
},
}
for _, test := range tests {
var buf bytes.Buffer
_, err := golangOrgAbsLinkReplacer.WriteString(&buf, test.input)
if err != nil {
t.Errorf("unexpected error during replacing links. Got: %#v, Want: nil.\n", err)
continue
}
if got, want := buf.String(), test.output; got != want {
t.Errorf("WriteString(%q) = %q. Expected: %q", test.input, got, want)
}
}
}
+184
View File
@@ -0,0 +1,184 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"flag"
"fmt"
"os"
"sort"
"strconv"
"text/tabwriter"
"golang.org/x/tools/benchmark/parse"
)
var (
changedOnly = flag.Bool("changed", false, "show only benchmarks that have changed")
magSort = flag.Bool("mag", false, "sort benchmarks by magnitude of change")
best = flag.Bool("best", false, "compare best times from old and new")
)
const usageFooter = `
Each input file should be from:
go test -run=NONE -bench=. > [old,new].txt
Benchcmp compares old and new for each benchmark.
If -test.benchmem=true is added to the "go test" command
benchcmp will also compare memory allocations.
`
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "usage: %s old.txt new.txt\n\n", os.Args[0])
flag.PrintDefaults()
fmt.Fprint(os.Stderr, usageFooter)
os.Exit(2)
}
flag.Parse()
if flag.NArg() != 2 {
flag.Usage()
}
before := parseFile(flag.Arg(0))
after := parseFile(flag.Arg(1))
cmps, warnings := Correlate(before, after)
for _, warn := range warnings {
fmt.Fprintln(os.Stderr, warn)
}
if len(cmps) == 0 {
fatal("benchcmp: no repeated benchmarks")
}
w := new(tabwriter.Writer)
w.Init(os.Stdout, 0, 0, 5, ' ', 0)
defer w.Flush()
var header bool // Has the header has been displayed yet for a given block?
if *magSort {
sort.Sort(ByDeltaNsPerOp(cmps))
} else {
sort.Sort(ByParseOrder(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(parse.NsPerOp) {
continue
}
if delta := cmp.DeltaNsPerOp(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprint(w, "benchmark\told ns/op\tnew ns/op\tdelta\n")
header = true
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", cmp.Name(), formatNs(cmp.Before.NsPerOp), formatNs(cmp.After.NsPerOp), delta.Percent())
}
}
header = false
if *magSort {
sort.Sort(ByDeltaMBPerS(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(parse.MBPerS) {
continue
}
if delta := cmp.DeltaMBPerS(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprint(w, "\nbenchmark\told MB/s\tnew MB/s\tspeedup\n")
header = true
}
fmt.Fprintf(w, "%s\t%.2f\t%.2f\t%s\n", cmp.Name(), cmp.Before.MBPerS, cmp.After.MBPerS, delta.Multiple())
}
}
header = false
if *magSort {
sort.Sort(ByDeltaAllocsPerOp(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(parse.AllocsPerOp) {
continue
}
if delta := cmp.DeltaAllocsPerOp(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprint(w, "\nbenchmark\told allocs\tnew allocs\tdelta\n")
header = true
}
fmt.Fprintf(w, "%s\t%d\t%d\t%s\n", cmp.Name(), cmp.Before.AllocsPerOp, cmp.After.AllocsPerOp, delta.Percent())
}
}
header = false
if *magSort {
sort.Sort(ByDeltaAllocedBytesPerOp(cmps))
}
for _, cmp := range cmps {
if !cmp.Measured(parse.AllocedBytesPerOp) {
continue
}
if delta := cmp.DeltaAllocedBytesPerOp(); !*changedOnly || delta.Changed() {
if !header {
fmt.Fprint(w, "\nbenchmark\told bytes\tnew bytes\tdelta\n")
header = true
}
fmt.Fprintf(w, "%s\t%d\t%d\t%s\n", cmp.Name(), cmp.Before.AllocedBytesPerOp, cmp.After.AllocedBytesPerOp, cmp.DeltaAllocedBytesPerOp().Percent())
}
}
}
func fatal(msg interface{}) {
fmt.Fprintln(os.Stderr, msg)
os.Exit(1)
}
func parseFile(path string) parse.Set {
f, err := os.Open(path)
if err != nil {
fatal(err)
}
defer f.Close()
bb, err := parse.ParseSet(f)
if err != nil {
fatal(err)
}
if *best {
selectBest(bb)
}
return bb
}
func selectBest(bs parse.Set) {
for name, bb := range bs {
if len(bb) < 2 {
continue
}
ord := bb[0].Ord
best := bb[0]
for _, b := range bb {
if b.NsPerOp < best.NsPerOp {
b.Ord = ord
best = b
}
}
bs[name] = []*parse.Benchmark{best}
}
}
// formatNs formats ns measurements to expose a useful amount of
// precision. It mirrors the ns precision logic of testing.B.
func formatNs(ns float64) string {
prec := 0
switch {
case ns < 10:
prec = 2
case ns < 100:
prec = 1
}
return strconv.FormatFloat(ns, 'f', prec, 64)
}
+59
View File
@@ -0,0 +1,59 @@
package main
import (
"reflect"
"testing"
"golang.org/x/tools/benchmark/parse"
)
func TestSelectBest(t *testing.T) {
have := parse.Set{
"Benchmark1": []*parse.Benchmark{
{
Name: "Benchmark1",
N: 10, NsPerOp: 100, Measured: parse.NsPerOp,
Ord: 0,
},
{
Name: "Benchmark1",
N: 10, NsPerOp: 50, Measured: parse.NsPerOp,
Ord: 3,
},
},
"Benchmark2": []*parse.Benchmark{
{
Name: "Benchmark2",
N: 10, NsPerOp: 60, Measured: parse.NsPerOp,
Ord: 1,
},
{
Name: "Benchmark2",
N: 10, NsPerOp: 500, Measured: parse.NsPerOp,
Ord: 2,
},
},
}
want := parse.Set{
"Benchmark1": []*parse.Benchmark{
{
Name: "Benchmark1",
N: 10, NsPerOp: 50, Measured: parse.NsPerOp,
Ord: 0,
},
},
"Benchmark2": []*parse.Benchmark{
{
Name: "Benchmark2",
N: 10, NsPerOp: 60, Measured: parse.NsPerOp,
Ord: 1,
},
},
}
selectBest(have)
if !reflect.DeepEqual(want, have) {
t.Errorf("filtered bench set incorrectly, want %v have %v", want, have)
}
}
+156
View File
@@ -0,0 +1,156 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"math"
"golang.org/x/tools/benchmark/parse"
)
// BenchCmp is a pair of benchmarks.
type BenchCmp struct {
Before *parse.Benchmark
After *parse.Benchmark
}
// Correlate correlates benchmarks from two BenchSets.
func Correlate(before, after parse.Set) (cmps []BenchCmp, warnings []string) {
cmps = make([]BenchCmp, 0, len(after))
for name, beforebb := range before {
afterbb := after[name]
if len(beforebb) != len(afterbb) {
warnings = append(warnings, fmt.Sprintf("ignoring %s: before has %d instances, after has %d", name, len(beforebb), len(afterbb)))
continue
}
for i, beforeb := range beforebb {
afterb := afterbb[i]
cmps = append(cmps, BenchCmp{beforeb, afterb})
}
}
return
}
func (c BenchCmp) Name() string { return c.Before.Name }
func (c BenchCmp) String() string { return fmt.Sprintf("<%s, %s>", c.Before, c.After) }
func (c BenchCmp) Measured(flag int) bool { return (c.Before.Measured & c.After.Measured & flag) != 0 }
func (c BenchCmp) DeltaNsPerOp() Delta { return Delta{c.Before.NsPerOp, c.After.NsPerOp} }
func (c BenchCmp) DeltaMBPerS() Delta { return Delta{c.Before.MBPerS, c.After.MBPerS} }
func (c BenchCmp) DeltaAllocedBytesPerOp() Delta {
return Delta{float64(c.Before.AllocedBytesPerOp), float64(c.After.AllocedBytesPerOp)}
}
func (c BenchCmp) DeltaAllocsPerOp() Delta {
return Delta{float64(c.Before.AllocsPerOp), float64(c.After.AllocsPerOp)}
}
// Delta is the before and after value for a benchmark measurement.
// Both must be non-negative.
type Delta struct {
Before float64
After float64
}
// mag calculates the magnitude of a change, regardless of the direction of
// the change. mag is intended for sorting and has no independent meaning.
func (d Delta) mag() float64 {
switch {
case d.Before != 0 && d.After != 0 && d.Before >= d.After:
return d.After / d.Before
case d.Before != 0 && d.After != 0 && d.Before < d.After:
return d.Before / d.After
case d.Before == 0 && d.After == 0:
return 1
default:
// 0 -> 1 or 1 -> 0
// These are significant changes and worth surfacing.
return math.Inf(1)
}
}
// Changed reports whether the benchmark quantities are different.
func (d Delta) Changed() bool { return d.Before != d.After }
// Float64 returns After / Before. If Before is 0, Float64 returns
// 1 if After is also 0, and +Inf otherwise.
func (d Delta) Float64() float64 {
switch {
case d.Before != 0:
return d.After / d.Before
case d.After == 0:
return 1
default:
return math.Inf(1)
}
}
// Percent formats a Delta as a percent change, ranging from -100% up.
func (d Delta) Percent() string {
return fmt.Sprintf("%+.2f%%", 100*d.Float64()-100)
}
// Multiple formats a Delta as a multiplier, ranging from 0.00x up.
func (d Delta) Multiple() string {
return fmt.Sprintf("%.2fx", d.Float64())
}
func (d Delta) String() string {
return fmt.Sprintf("Δ(%f, %f)", d.Before, d.After)
}
// ByParseOrder sorts BenchCmps to match the order in
// which the Before benchmarks were presented to Parse.
type ByParseOrder []BenchCmp
func (x ByParseOrder) Len() int { return len(x) }
func (x ByParseOrder) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByParseOrder) Less(i, j int) bool { return x[i].Before.Ord < x[j].Before.Ord }
// lessByDelta provides lexicographic ordering:
// * largest delta by magnitude
// * alphabetic by name
func lessByDelta(i, j BenchCmp, calcDelta func(BenchCmp) Delta) bool {
iDelta, jDelta := calcDelta(i).mag(), calcDelta(j).mag()
if iDelta != jDelta {
return iDelta < jDelta
}
return i.Name() < j.Name()
}
// ByDeltaNsPerOp sorts BenchCmps lexicographically by change
// in ns/op, descending, then by benchmark name.
type ByDeltaNsPerOp []BenchCmp
func (x ByDeltaNsPerOp) Len() int { return len(x) }
func (x ByDeltaNsPerOp) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaNsPerOp) Less(i, j int) bool { return lessByDelta(x[i], x[j], BenchCmp.DeltaNsPerOp) }
// ByDeltaMBPerS sorts BenchCmps lexicographically by change
// in MB/s, descending, then by benchmark name.
type ByDeltaMBPerS []BenchCmp
func (x ByDeltaMBPerS) Len() int { return len(x) }
func (x ByDeltaMBPerS) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaMBPerS) Less(i, j int) bool { return lessByDelta(x[i], x[j], BenchCmp.DeltaMBPerS) }
// ByDeltaAllocedBytesPerOp sorts BenchCmps lexicographically by change
// in B/op, descending, then by benchmark name.
type ByDeltaAllocedBytesPerOp []BenchCmp
func (x ByDeltaAllocedBytesPerOp) Len() int { return len(x) }
func (x ByDeltaAllocedBytesPerOp) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaAllocedBytesPerOp) Less(i, j int) bool {
return lessByDelta(x[i], x[j], BenchCmp.DeltaAllocedBytesPerOp)
}
// ByDeltaAllocsPerOp sorts BenchCmps lexicographically by change
// in allocs/op, descending, then by benchmark name.
type ByDeltaAllocsPerOp []BenchCmp
func (x ByDeltaAllocsPerOp) Len() int { return len(x) }
func (x ByDeltaAllocsPerOp) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x ByDeltaAllocsPerOp) Less(i, j int) bool {
return lessByDelta(x[i], x[j], BenchCmp.DeltaAllocsPerOp)
}

Some files were not shown because too many files have changed in this diff Show More