mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
* add minio-go dep, update deps * add minio s3 client minio has an s3 compatible api and is an open source project and, notably, is not amazon, so it seems best to use their client (fwiw the aws-sdk-go is a giant hair ball of things we don't need, too). it was pretty easy and seems to work, so rolling with it. also, minio is a totally feasible option for fn installs in prod / for demos / for local. * adds 's3' package for s3 compatible log storage api, for use with storing logs from calls and retrieving them. * removes DELETE /v1/apps/:app/calls/:call/log endpoint * removes internal log deletion api * changes the GetLog API to use an io.Reader, which is a backwards step atm due to the json api for logs, I have another branch lined up to make a plain text log API and this will be much more efficient (also want to gzip) * hooked up minio to the test suite and fixed up the test suite * add how to run minio docs and point fn at it docs some notes: notably we aren't cleaning up these logs. there is a ticket already to make a Mr. Clean who wakes up periodically and nukes old stuff, so am punting any api design around some kind of TTL deletion of logs. there are a lot of options really for Mr. Clean, we can notably defer to him when apps are deleted, too, so that app deletion is fast and then Mr. Clean will just clean them up later (seems like a good option). have not tested against BMC object store, which has an s3 compatible API. but in theory it 'just works' (the reason for doing this). in any event, that's part of the service land to figure out. closes #481 closes #473 * add log not found error to minio land
492 lines
12 KiB
Go
492 lines
12 KiB
Go
// Copyright 2014 Unknwon
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||
// not use this file except in compliance with the License. You may obtain
|
||
// a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||
// License for the specific language governing permissions and limitations
|
||
// under the License.
|
||
|
||
package ini
|
||
|
||
import (
|
||
"bytes"
|
||
"io/ioutil"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
. "github.com/smartystreets/goconvey/convey"
|
||
)
|
||
|
||
func Test_Version(t *testing.T) {
|
||
Convey("Get version", t, func() {
|
||
So(Version(), ShouldEqual, _VERSION)
|
||
})
|
||
}
|
||
|
||
const _CONF_DATA = `
|
||
; Package name
|
||
NAME = ini
|
||
; Package version
|
||
VERSION = v1
|
||
; Package import path
|
||
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
|
||
|
||
# Information about package author
|
||
# Bio can be written in multiple lines.
|
||
[author]
|
||
NAME = Unknwon ; Succeeding comment
|
||
E-MAIL = fake@localhost
|
||
GITHUB = https://github.com/%(NAME)s
|
||
BIO = """Gopher.
|
||
Coding addict.
|
||
Good man.
|
||
""" # Succeeding comment
|
||
|
||
[package]
|
||
CLONE_URL = https://%(IMPORT_PATH)s
|
||
|
||
[package.sub]
|
||
UNUSED_KEY = should be deleted
|
||
|
||
[features]
|
||
-: Support read/write comments of keys and sections
|
||
-: Support auto-increment of key names
|
||
-: Support load multiple files to overwrite key values
|
||
|
||
[types]
|
||
STRING = str
|
||
BOOL = true
|
||
BOOL_FALSE = false
|
||
FLOAT64 = 1.25
|
||
INT = 10
|
||
TIME = 2015-01-01T20:17:05Z
|
||
DURATION = 2h45m
|
||
UINT = 3
|
||
|
||
[array]
|
||
STRINGS = en, zh, de
|
||
FLOAT64S = 1.1, 2.2, 3.3
|
||
INTS = 1, 2, 3
|
||
UINTS = 1, 2, 3
|
||
TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
|
||
|
||
[note]
|
||
empty_lines = next line is empty\
|
||
|
||
; Comment before the section
|
||
[comments] ; This is a comment for the section too
|
||
; Comment before key
|
||
key = "value"
|
||
key2 = "value2" ; This is a comment for key2
|
||
key3 = "one", "two", "three"
|
||
|
||
[advance]
|
||
value with quotes = "some value"
|
||
value quote2 again = 'some value'
|
||
includes comment sign = ` + "`" + "my#password" + "`" + `
|
||
includes comment sign2 = ` + "`" + "my;password" + "`" + `
|
||
true = 2+3=5
|
||
"1+1=2" = true
|
||
"""6+1=7""" = true
|
||
"""` + "`" + `5+5` + "`" + `""" = 10
|
||
` + "`" + `"6+6"` + "`" + ` = 12
|
||
` + "`" + `7-2=4` + "`" + ` = false
|
||
ADDRESS = ` + "`" + `404 road,
|
||
NotFound, State, 50000` + "`" + `
|
||
|
||
two_lines = how about \
|
||
continuation lines?
|
||
lots_of_lines = 1 \
|
||
2 \
|
||
3 \
|
||
4 \
|
||
`
|
||
|
||
func Test_Load(t *testing.T) {
|
||
Convey("Load from data sources", t, func() {
|
||
|
||
Convey("Load with empty data", func() {
|
||
So(Empty(), ShouldNotBeNil)
|
||
})
|
||
|
||
Convey("Load with multiple data sources", func() {
|
||
cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini", ioutil.NopCloser(bytes.NewReader([]byte(_CONF_DATA))))
|
||
So(err, ShouldBeNil)
|
||
So(cfg, ShouldNotBeNil)
|
||
|
||
f, err := Load([]byte(_CONF_DATA), "testdata/404.ini")
|
||
So(err, ShouldNotBeNil)
|
||
So(f, ShouldBeNil)
|
||
})
|
||
|
||
Convey("Load with io.ReadCloser", func() {
|
||
cfg, err := Load(ioutil.NopCloser(bytes.NewReader([]byte(_CONF_DATA))))
|
||
So(err, ShouldBeNil)
|
||
So(cfg, ShouldNotBeNil)
|
||
|
||
So(cfg.Section("").Key("NAME").String(), ShouldEqual, "ini")
|
||
})
|
||
})
|
||
|
||
Convey("Bad load process", t, func() {
|
||
|
||
Convey("Load from invalid data sources", func() {
|
||
_, err := Load(_CONF_DATA)
|
||
So(err, ShouldNotBeNil)
|
||
|
||
f, err := Load("testdata/404.ini")
|
||
So(err, ShouldNotBeNil)
|
||
So(f, ShouldBeNil)
|
||
|
||
_, err = Load(1)
|
||
So(err, ShouldNotBeNil)
|
||
|
||
_, err = Load([]byte(""), 1)
|
||
So(err, ShouldNotBeNil)
|
||
})
|
||
|
||
Convey("Load with bad section name", func() {
|
||
_, err := Load([]byte("[]"))
|
||
So(err, ShouldNotBeNil)
|
||
|
||
_, err = Load([]byte("["))
|
||
So(err, ShouldNotBeNil)
|
||
})
|
||
|
||
Convey("Load with bad keys", func() {
|
||
_, err := Load([]byte(`"""name`))
|
||
So(err, ShouldNotBeNil)
|
||
|
||
_, err = Load([]byte(`"""name"""`))
|
||
So(err, ShouldNotBeNil)
|
||
|
||
_, err = Load([]byte(`""=1`))
|
||
So(err, ShouldNotBeNil)
|
||
|
||
_, err = Load([]byte(`=`))
|
||
So(err, ShouldNotBeNil)
|
||
|
||
_, err = Load([]byte(`name`))
|
||
So(err, ShouldNotBeNil)
|
||
})
|
||
|
||
Convey("Load with bad values", func() {
|
||
_, err := Load([]byte(`name="""Unknwon`))
|
||
So(err, ShouldNotBeNil)
|
||
})
|
||
})
|
||
|
||
Convey("Get section and key insensitively", t, func() {
|
||
cfg, err := InsensitiveLoad([]byte(_CONF_DATA), "testdata/conf.ini")
|
||
So(err, ShouldBeNil)
|
||
So(cfg, ShouldNotBeNil)
|
||
|
||
sec, err := cfg.GetSection("Author")
|
||
So(err, ShouldBeNil)
|
||
So(sec, ShouldNotBeNil)
|
||
|
||
key, err := sec.GetKey("E-mail")
|
||
So(err, ShouldBeNil)
|
||
So(key, ShouldNotBeNil)
|
||
})
|
||
|
||
Convey("Load with ignoring continuation lines", t, func() {
|
||
cfg, err := LoadSources(LoadOptions{IgnoreContinuation: true}, []byte(`key1=a\b\
|
||
key2=c\d\`))
|
||
So(err, ShouldBeNil)
|
||
So(cfg, ShouldNotBeNil)
|
||
|
||
So(cfg.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
|
||
So(cfg.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
|
||
})
|
||
|
||
Convey("Load with ignoring inline comments", t, func() {
|
||
cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, []byte(`key1=value ;comment
|
||
key2=value #comment2`))
|
||
So(err, ShouldBeNil)
|
||
So(cfg, ShouldNotBeNil)
|
||
|
||
So(cfg.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
|
||
So(cfg.Section("").Key("key2").String(), ShouldEqual, `value #comment2`)
|
||
|
||
var buf bytes.Buffer
|
||
cfg.WriteTo(&buf)
|
||
So(buf.String(), ShouldEqual, `key1 = value ;comment
|
||
key2 = value #comment2
|
||
|
||
`)
|
||
})
|
||
|
||
Convey("Load with boolean type keys", t, func() {
|
||
cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, []byte(`key1=hello
|
||
key2
|
||
#key3
|
||
key4
|
||
key5`))
|
||
So(err, ShouldBeNil)
|
||
So(cfg, ShouldNotBeNil)
|
||
|
||
So(strings.Join(cfg.Section("").KeyStrings(), ","), ShouldEqual, "key1,key2,key4,key5")
|
||
So(cfg.Section("").Key("key2").MustBool(false), ShouldBeTrue)
|
||
|
||
var buf bytes.Buffer
|
||
cfg.WriteTo(&buf)
|
||
// there is always a trailing \n at the end of the section
|
||
So(buf.String(), ShouldEqual, `key1 = hello
|
||
key2
|
||
#key3
|
||
key4
|
||
key5
|
||
`)
|
||
})
|
||
}
|
||
|
||
func Test_File_ChildSections(t *testing.T) {
|
||
Convey("Find child sections by parent name", t, func() {
|
||
cfg, err := Load([]byte(`
|
||
[node]
|
||
|
||
[node.biz1]
|
||
|
||
[node.biz2]
|
||
|
||
[node.biz3]
|
||
|
||
[node.bizN]
|
||
`))
|
||
So(err, ShouldBeNil)
|
||
So(cfg, ShouldNotBeNil)
|
||
|
||
children := cfg.ChildSections("node")
|
||
names := make([]string, len(children))
|
||
for i := range children {
|
||
names[i] = children[i].name
|
||
}
|
||
So(strings.Join(names, ","), ShouldEqual, "node.biz1,node.biz2,node.biz3,node.bizN")
|
||
})
|
||
}
|
||
|
||
func Test_LooseLoad(t *testing.T) {
|
||
Convey("Loose load from data sources", t, func() {
|
||
Convey("Loose load mixed with nonexistent file", func() {
|
||
cfg, err := LooseLoad("testdata/404.ini")
|
||
So(err, ShouldBeNil)
|
||
So(cfg, ShouldNotBeNil)
|
||
var fake struct {
|
||
Name string `ini:"name"`
|
||
}
|
||
So(cfg.MapTo(&fake), ShouldBeNil)
|
||
|
||
cfg, err = LooseLoad([]byte("name=Unknwon"), "testdata/404.ini")
|
||
So(err, ShouldBeNil)
|
||
So(cfg.Section("").Key("name").String(), ShouldEqual, "Unknwon")
|
||
So(cfg.MapTo(&fake), ShouldBeNil)
|
||
So(fake.Name, ShouldEqual, "Unknwon")
|
||
})
|
||
})
|
||
|
||
}
|
||
|
||
func Test_File_Append(t *testing.T) {
|
||
Convey("Append data sources", t, func() {
|
||
cfg, err := Load([]byte(""))
|
||
So(err, ShouldBeNil)
|
||
So(cfg, ShouldNotBeNil)
|
||
|
||
So(cfg.Append([]byte(""), []byte("")), ShouldBeNil)
|
||
|
||
Convey("Append bad data sources", func() {
|
||
So(cfg.Append(1), ShouldNotBeNil)
|
||
So(cfg.Append([]byte(""), 1), ShouldNotBeNil)
|
||
})
|
||
})
|
||
}
|
||
|
||
func Test_File_WriteTo(t *testing.T) {
|
||
Convey("Write to somewhere", t, func() {
|
||
var buf bytes.Buffer
|
||
cfg := Empty()
|
||
cfg.WriteTo(&buf)
|
||
})
|
||
}
|
||
|
||
func Test_File_SaveTo_WriteTo(t *testing.T) {
|
||
Convey("Save file", t, func() {
|
||
cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
|
||
So(err, ShouldBeNil)
|
||
So(cfg, ShouldNotBeNil)
|
||
|
||
cfg.Section("").Key("NAME").Comment = "Package name"
|
||
cfg.Section("author").Comment = `Information about package author
|
||
# Bio can be written in multiple lines.`
|
||
cfg.Section("advanced").Key("val w/ pound").SetValue("my#password")
|
||
cfg.Section("advanced").Key("longest key has a colon : yes/no").SetValue("yes")
|
||
So(cfg.SaveTo("testdata/conf_out.ini"), ShouldBeNil)
|
||
|
||
cfg.Section("author").Key("NAME").Comment = "This is author name"
|
||
|
||
So(cfg.SaveToIndent("testdata/conf_out.ini", "\t"), ShouldBeNil)
|
||
|
||
var buf bytes.Buffer
|
||
_, err = cfg.WriteToIndent(&buf, "\t")
|
||
So(err, ShouldBeNil)
|
||
So(buf.String(), ShouldEqual, `; Package name
|
||
NAME = ini
|
||
; Package version
|
||
VERSION = v1
|
||
; Package import path
|
||
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
|
||
|
||
; Information about package author
|
||
# Bio can be written in multiple lines.
|
||
[author]
|
||
; This is author name
|
||
NAME = Unknwon
|
||
E-MAIL = u@gogs.io
|
||
GITHUB = https://github.com/%(NAME)s
|
||
# Succeeding comment
|
||
BIO = """Gopher.
|
||
Coding addict.
|
||
Good man.
|
||
"""
|
||
|
||
[package]
|
||
CLONE_URL = https://%(IMPORT_PATH)s
|
||
|
||
[package.sub]
|
||
UNUSED_KEY = should be deleted
|
||
|
||
[features]
|
||
- = Support read/write comments of keys and sections
|
||
- = Support auto-increment of key names
|
||
- = Support load multiple files to overwrite key values
|
||
|
||
[types]
|
||
STRING = str
|
||
BOOL = true
|
||
BOOL_FALSE = false
|
||
FLOAT64 = 1.25
|
||
INT = 10
|
||
TIME = 2015-01-01T20:17:05Z
|
||
DURATION = 2h45m
|
||
UINT = 3
|
||
|
||
[array]
|
||
STRINGS = en, zh, de
|
||
FLOAT64S = 1.1, 2.2, 3.3
|
||
INTS = 1, 2, 3
|
||
UINTS = 1, 2, 3
|
||
TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
|
||
|
||
[note]
|
||
empty_lines = next line is empty
|
||
|
||
; Comment before the section
|
||
; This is a comment for the section too
|
||
[comments]
|
||
; Comment before key
|
||
key = value
|
||
; This is a comment for key2
|
||
key2 = value2
|
||
key3 = "one", "two", "three"
|
||
|
||
[advance]
|
||
value with quotes = some value
|
||
value quote2 again = some value
|
||
includes comment sign = `+"`"+"my#password"+"`"+`
|
||
includes comment sign2 = `+"`"+"my;password"+"`"+`
|
||
true = 2+3=5
|
||
`+"`"+`1+1=2`+"`"+` = true
|
||
`+"`"+`6+1=7`+"`"+` = true
|
||
"""`+"`"+`5+5`+"`"+`""" = 10
|
||
`+"`"+`"6+6"`+"`"+` = 12
|
||
`+"`"+`7-2=4`+"`"+` = false
|
||
ADDRESS = """404 road,
|
||
NotFound, State, 50000"""
|
||
two_lines = how about continuation lines?
|
||
lots_of_lines = 1 2 3 4
|
||
|
||
[advanced]
|
||
val w/ pound = `+"`"+`my#password`+"`"+`
|
||
`+"`"+`longest key has a colon : yes/no`+"`"+` = yes
|
||
|
||
`)
|
||
})
|
||
}
|
||
|
||
func Test_File_WriteTo_SectionRaw(t *testing.T) {
|
||
Convey("Write a INI with a raw section", t, func() {
|
||
var buf bytes.Buffer
|
||
cfg, err := LoadSources(
|
||
LoadOptions{
|
||
UnparseableSections: []string{"CORE_LESSON", "COMMENTS"},
|
||
},
|
||
"testdata/aicc.ini")
|
||
So(err, ShouldBeNil)
|
||
So(cfg, ShouldNotBeNil)
|
||
cfg.WriteToIndent(&buf, "\t")
|
||
So(buf.String(), ShouldEqual, `[Core]
|
||
Lesson_Location = 87
|
||
Lesson_Status = C
|
||
Score = 3
|
||
Time = 00:02:30
|
||
|
||
[CORE_LESSON]
|
||
my lesson state data – 1111111111111111111000000000000000001110000
|
||
111111111111111111100000000000111000000000 – end my lesson state data
|
||
[COMMENTS]
|
||
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
|
||
`)
|
||
})
|
||
}
|
||
|
||
// Helpers for slice tests.
|
||
func float64sEqual(values []float64, expected ...float64) {
|
||
So(values, ShouldHaveLength, len(expected))
|
||
for i, v := range expected {
|
||
So(values[i], ShouldEqual, v)
|
||
}
|
||
}
|
||
|
||
func intsEqual(values []int, expected ...int) {
|
||
So(values, ShouldHaveLength, len(expected))
|
||
for i, v := range expected {
|
||
So(values[i], ShouldEqual, v)
|
||
}
|
||
}
|
||
|
||
func int64sEqual(values []int64, expected ...int64) {
|
||
So(values, ShouldHaveLength, len(expected))
|
||
for i, v := range expected {
|
||
So(values[i], ShouldEqual, v)
|
||
}
|
||
}
|
||
|
||
func uintsEqual(values []uint, expected ...uint) {
|
||
So(values, ShouldHaveLength, len(expected))
|
||
for i, v := range expected {
|
||
So(values[i], ShouldEqual, v)
|
||
}
|
||
}
|
||
|
||
func uint64sEqual(values []uint64, expected ...uint64) {
|
||
So(values, ShouldHaveLength, len(expected))
|
||
for i, v := range expected {
|
||
So(values[i], ShouldEqual, v)
|
||
}
|
||
}
|
||
|
||
func timesEqual(values []time.Time, expected ...time.Time) {
|
||
So(values, ShouldHaveLength, len(expected))
|
||
for i, v := range expected {
|
||
So(values[i].String(), ShouldEqual, v.String())
|
||
}
|
||
}
|