feat(climatecontrol): initial working version

This commit is contained in:
2021-01-13 11:03:02 +00:00
commit 2177e155c2
8 changed files with 635 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/*.tidy-check
/bin/*
/coverage.out
/output.txt

78
.golangci.yml Normal file
View File

@@ -0,0 +1,78 @@
linters-settings:
funlen:
lines: 100
statements: 150
gocyclo:
min-complexity: 20
golint:
min-confidence: 0
govet:
check-shadowing: true
enable-all: true
lll:
line-length: 80
tab-width: 4
maligned:
suggest-new: true
misspell:
locale: US
linters:
disable-all: true
enable:
- bodyclose
- deadcode
- depguard
- dupl
- errcheck
- funlen
- gochecknoinits
- goconst
- gocritic
- gocyclo
- goerr113
- goimports
- golint
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- lll
- misspell
- nakedret
- nlreturn
- noctx
- nolintlint
- scopelint
- sqlclosecheck
- staticcheck
- structcheck
- typecheck
- unconvert
- unused
- varcheck
- whitespace
issues:
include:
# - EXC0002 # disable excluding of issues about comments from golint
exclude:
- Using the variable on range scope `tt` in function literal
- Using the variable on range scope `tc` in function literal
exclude-rules:
- path: "_test\\.go"
linters:
- funlen
- dupl
- source: "^//go:generate "
linters:
- lll
- source: "`json:"
linters:
- lll
run:
timeout: 2m
allow-parallel-runners: true
modules-download-mode: readonly

175
Makefile Normal file
View File

@@ -0,0 +1,175 @@
GOMODNAME := $(shell grep 'module' go.mod | sed -e 's/^module //')
SOURCES := $(shell find . -name "*.go" -or -name "go.mod" -or -name "go.sum" \
-or -name "Makefile")
# Verbose output
ifdef VERBOSE
V = -v
endif
#
# Environment
#
BINDIR := bin
TOOLDIR := $(BINDIR)/tools
# Global environment variables for all targets
SHELL ?= /bin/bash
SHELL := env \
GO111MODULE=on \
GOBIN=$(CURDIR)/$(TOOLDIR) \
CGO_ENABLED=1 \
PATH='$(CURDIR)/$(BINDIR):$(CURDIR)/$(TOOLDIR):$(PATH)' \
$(SHELL)
#
# Defaults
#
# Default target
.DEFAULT_GOAL := test
#
# Tools
#
TOOLS += $(TOOLDIR)/gobin
gobin: $(TOOLDIR)/gobin
$(TOOLDIR)/gobin:
GO111MODULE=off go get -u github.com/myitcv/gobin
# external tool
define tool # 1: binary-name, 2: go-import-path
TOOLS += $(TOOLDIR)/$(1)
.PHONY: $(1)
$(1): $(TOOLDIR)/$(1)
$(TOOLDIR)/$(1): $(TOOLDIR)/gobin Makefile
gobin $(V) "$(2)"
endef
$(eval $(call tool,godoc,golang.org/x/tools/cmd/godoc))
$(eval $(call tool,gofumports,mvdan.cc/gofumpt/gofumports))
$(eval $(call tool,golangci-lint,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.35))
.PHONY: tools
tools: $(TOOLS)
#
# Development
#
TEST ?= $$(go list ./... | grep -v 'vendor')
BENCH ?= .
.PHONY: clean
clean:
rm -f $(TOOLS)
rm -f ./coverage.out ./go.mod.tidy-check ./go.sum.tidy-check
.PHONY: clean-golden
clean-golden:
rm -f $(shell find * -path '*/testdata/*' -name "*.golden" \
-exec echo "'{}'" \;)
.PHONY: test
test:
go test $(V) -count=1 -race $(TESTARGS) $(TEST)
.PHONY: test-update-golden
test-update-golden:
@$(MAKE) test UPDATE_GOLDEN=1
.PHONY: regen-golden
regen-golden: clean-golden test-update-golden
.PHONY: test-deps
test-deps:
go test all
.PHONY: lint
lint: golangci-lint
GOGC=off golangci-lint $(V) run
.PHONY: format
format: gofumports
gofumports -w .
.SILENT: bench
.PHONY: bench
bench:
go test $(V) -count=1 -bench=$(BENCH) $(TESTARGS) $(TEST)
#
# Coverage
#
.PHONY: cov
cov: coverage.out
.PHONY: cov-html
cov-html: coverage.out
go tool cover -html=./coverage.out
.PHONY: cov-func
cov-func: coverage.out
go tool cover -func=./coverage.out
coverage.out: $(SOURCES)
go test $(V) -covermode=count -coverprofile=./coverage.out ./...
#
# Dependencies
#
.PHONY: deps
deps:
$(info Downloading dependencies)
go mod download
.PHONY: tidy
tidy:
go mod tidy $(V)
.PHONY: verify
verify:
go mod verify
.SILENT: check-tidy
.PHONY: check-tidy
check-tidy:
cp go.mod go.mod.tidy-check
cp go.sum go.sum.tidy-check
go mod tidy
( \
diff go.mod go.mod.tidy-check && \
diff go.sum go.sum.tidy-check && \
rm -f go.mod go.sum && \
mv go.mod.tidy-check go.mod && \
mv go.sum.tidy-check go.sum \
) || ( \
rm -f go.mod go.sum && \
mv go.mod.tidy-check go.mod && \
mv go.sum.tidy-check go.sum; \
exit 1 \
)
#
# Documentation
#
# Serve docs
.PHONY: docs
docs: godoc
$(info serviing docs on http://127.0.0.1:6060/pkg/$(GOMODNAME)/)
@godoc -http=127.0.0.1:6060
#
# Release
#
.PHONY: new-version
new-version:
npx standard-version

77
climatecontrol.go Normal file
View File

@@ -0,0 +1,77 @@
// Package climatecontrol provides test helper functions which temporary changes
// environment variables accessible via os.Getenv and os.LookupEnv. After they
// are done, all changes to environment variables are reset.
package climatecontrol
import (
"os"
"strings"
"sync"
)
var mux = &sync.Mutex{}
// WithEnv temporarily sets all given vars as environment variables during the
// execution of f function. Existing environment variables are also available
// within f. Any overridden environment variables will contain the overridden
// value..
//
// After f execution completes all changes to environment variables are reset,
// including manual changes within the f function.
func WithEnv(vars map[string]string, f func()) {
mux.Lock()
defer mux.Unlock()
undo := parseEnviron(os.Environ())
apply(vars)
defer func() {
os.Clearenv()
apply(undo)
}()
f()
}
// WithCleanEnv temporarily changes all environment variables available within f
// function to only be those provided. Existing environment variables are not
// available within f.
//
// After f execution completes all changes to environment variables are reset,
// including manual changes within the f function.
func WithCleanEnv(vars map[string]string, f func()) {
mux.Lock()
defer mux.Unlock()
undo := parseEnviron(os.Environ())
os.Clearenv()
apply(vars)
defer func() {
os.Clearenv()
apply(undo)
}()
f()
}
func apply(vars map[string]string) {
for k, v := range vars {
os.Setenv(k, v)
}
}
func parseEnviron(vars []string) map[string]string {
r := map[string]string{}
for _, v := range vars {
i := strings.Index(v, "=")
if i < 1 {
continue
}
r[v[0:i]] = v[i+1:]
}
return r
}

View File

@@ -0,0 +1,108 @@
package climatecontrol_test
import (
"fmt"
"os"
cc "github.com/jimeh/climatecontrol"
)
func ExampleWithEnv() {
// existing environment variables
os.Setenv("MYAPP_HOSTNAME", "myapp.com")
os.Setenv("MYAPP_PORT", "80")
fmt.Println("Before:")
fmt.Printf(" - MYAPP_HOSTNAME=%s\n", os.Getenv("MYAPP_HOSTNAME"))
fmt.Printf(" - MYAPP_PORT=%s\n", os.Getenv("MYAPP_PORT"))
fmt.Printf(" - MYAPP_THEME=%s\n", os.Getenv("MYAPP_THEME"))
fmt.Printf(" - MYAPP_TESTING=%s\n", os.Getenv("MYAPP_TESTING"))
// temporary environment variables
env := map[string]string{
"MYAPP_HOSTNAME": "testing-myapp.test",
"MYAPP_TESTING": "unit",
}
cc.WithEnv(env, func() {
os.Setenv("MYAPP_THEME", "dark")
fmt.Println("Inside func:")
fmt.Printf(" - MYAPP_HOSTNAME=%s\n", os.Getenv("MYAPP_HOSTNAME"))
fmt.Printf(" - MYAPP_PORT=%s\n", os.Getenv("MYAPP_PORT"))
fmt.Printf(" - MYAPP_THEME=%s\n", os.Getenv("MYAPP_THEME"))
fmt.Printf(" - MYAPP_TESTING=%s\n", os.Getenv("MYAPP_TESTING"))
})
// original environment variables restored
fmt.Println("After:")
fmt.Printf(" - MYAPP_HOSTNAME=%s\n", os.Getenv("MYAPP_HOSTNAME"))
fmt.Printf(" - MYAPP_PORT=%s\n", os.Getenv("MYAPP_PORT"))
fmt.Printf(" - MYAPP_THEME=%s\n", os.Getenv("MYAPP_THEME"))
fmt.Printf(" - MYAPP_TESTING=%s\n", os.Getenv("MYAPP_TESTING"))
// Output:
// Before:
// - MYAPP_HOSTNAME=myapp.com
// - MYAPP_PORT=80
// - MYAPP_THEME=
// - MYAPP_TESTING=
// Inside func:
// - MYAPP_HOSTNAME=testing-myapp.test
// - MYAPP_PORT=80
// - MYAPP_THEME=dark
// - MYAPP_TESTING=unit
// After:
// - MYAPP_HOSTNAME=myapp.com
// - MYAPP_PORT=80
// - MYAPP_THEME=
// - MYAPP_TESTING=
}
func ExampleWithCleanEnv() {
// existing environment variables
os.Setenv("MYAPP_HOSTNAME", "myapp.com")
os.Setenv("MYAPP_PORT", "80")
fmt.Println("Before:")
fmt.Printf(" - MYAPP_HOSTNAME=%s\n", os.Getenv("MYAPP_HOSTNAME"))
fmt.Printf(" - MYAPP_PORT=%s\n", os.Getenv("MYAPP_PORT"))
fmt.Printf(" - MYAPP_THEME=%s\n", os.Getenv("MYAPP_THEME"))
fmt.Printf(" - MYAPP_TESTING=%s\n", os.Getenv("MYAPP_TESTING"))
// temporary environment variables
env := map[string]string{
"MYAPP_HOSTNAME": "testing-myapp.test",
"MYAPP_TESTING": "unit",
}
cc.WithCleanEnv(env, func() {
os.Setenv("MYAPP_THEME", "dark")
fmt.Println("Inside func:")
fmt.Printf(" - MYAPP_HOSTNAME=%s\n", os.Getenv("MYAPP_HOSTNAME"))
fmt.Printf(" - MYAPP_PORT=%s\n", os.Getenv("MYAPP_PORT"))
fmt.Printf(" - MYAPP_THEME=%s\n", os.Getenv("MYAPP_THEME"))
fmt.Printf(" - MYAPP_TESTING=%s\n", os.Getenv("MYAPP_TESTING"))
})
// original environme
fmt.Println("After:")
fmt.Printf(" - MYAPP_HOSTNAME=%s\n", os.Getenv("MYAPP_HOSTNAME"))
fmt.Printf(" - MYAPP_PORT=%s\n", os.Getenv("MYAPP_PORT"))
fmt.Printf(" - MYAPP_THEME=%s\n", os.Getenv("MYAPP_THEME"))
fmt.Printf(" - MYAPP_TESTING=%s\n", os.Getenv("MYAPP_TESTING"))
// Output:
// Before:
// - MYAPP_HOSTNAME=myapp.com
// - MYAPP_PORT=80
// - MYAPP_THEME=
// - MYAPP_TESTING=
// Inside func:
// - MYAPP_HOSTNAME=testing-myapp.test
// - MYAPP_PORT=
// - MYAPP_THEME=dark
// - MYAPP_TESTING=unit
// After:
// - MYAPP_HOSTNAME=myapp.com
// - MYAPP_PORT=80
// - MYAPP_THEME=
// - MYAPP_TESTING=
}

178
climatecontrol_test.go Normal file
View File

@@ -0,0 +1,178 @@
package climatecontrol
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWithEnv(t *testing.T) {
tests := []struct {
name string
env map[string]string
}{
{
name: "empty",
env: map[string]string{},
},
{
name: "new vars",
env: map[string]string{
"CC_TEST_INSIDE": "new var is here",
"CC_TEST_FOO": "bar",
},
},
{
name: "set existing",
env: map[string]string{
"CC_TEST_INSIDE": "new var is here",
"CC_TEST_OUTSIDE": "this is not the same",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
os.Setenv("CC_TEST_OUTSIDE", t.Name())
outside := parseEnviron(os.Environ())
WithEnv(tt.env, func() {
os.Setenv("CC_TEST_SET_INSIDE", t.Name()+"-manual")
for k, v := range tt.env {
got := os.Getenv(k)
assert.Equal(t, v, got)
}
for k, v := range outside {
if _, ok := tt.env[k]; !ok {
assert.Equal(t, v, os.Getenv(k))
}
}
})
_, exists := os.LookupEnv("CC_TEST_SET_INSIDE")
assert.Equal(t, false, exists)
for k := range tt.env {
if _, ok := outside[k]; !ok {
_, exists := os.LookupEnv(k)
assert.Equal(t, false, exists)
}
}
for k, v := range outside {
assert.Equal(t, v, os.Getenv(k))
}
os.Unsetenv("CC_TEST_OUTSIDE")
})
}
}
func TestWithCleanEnv(t *testing.T) {
tests := []struct {
name string
env map[string]string
}{
{
name: "empty",
env: map[string]string{},
},
{
name: "new vars",
env: map[string]string{
"CC_TEST_INSIDE": "new var is here",
"CC_TEST_FOO": "bar",
},
},
{
name: "set existing",
env: map[string]string{
"CC_TEST_INSIDE": "new var is here",
"CC_TEST_OUTSIDE": "is is not the same",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
os.Setenv("CC_TEST_OUTSIDE", t.Name())
outside := parseEnviron(os.Environ())
WithCleanEnv(tt.env, func() {
os.Setenv("CC_TEST_SET_INSIDE", t.Name()+"-manual")
for k, v := range tt.env {
got := os.Getenv(k)
assert.Equal(t, v, got)
}
for k := range outside {
if _, ok := tt.env[k]; !ok {
_, exists := os.LookupEnv(k)
assert.Equal(t, false, exists)
}
}
})
_, exists := os.LookupEnv("CC_TEST_SET_INSIDE")
assert.Equal(t, false, exists)
for k := range tt.env {
if _, ok := outside[k]; !ok {
_, exists := os.LookupEnv(k)
assert.Equal(t, false, exists)
}
}
for k, v := range outside {
assert.Equal(t, v, os.Getenv(k))
}
os.Unsetenv("CC_TEST_OUTSIDE")
})
}
}
func Test_parseEnviron(t *testing.T) {
tests := []struct {
name string
vars []string
want map[string]string
}{
{
name: "empty",
vars: []string{},
want: map[string]string{},
},
{
name: "various",
vars: []string{
"USER=john",
"NAME=John Doe",
"SHELL=/bin/bash",
"GOPRIVATE=",
"X=11",
"TAGS=foo=bar,hello=world",
"_=go",
"INVALID-var",
},
want: map[string]string{
"USER": "john",
"NAME": "John Doe",
"SHELL": "/bin/bash",
"GOPRIVATE": "",
"X": "11",
"TAGS": "foo=bar,hello=world",
"_": "go",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := parseEnviron(tt.vars)
assert.Equal(t, tt.want, got)
})
}
}

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module github.com/jimeh/climatecontrol
go 1.15
require github.com/stretchr/testify v1.6.1

10
go.sum Normal file
View File

@@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=