How would I go about having a package register some object (for instance a function) to a registry at load time such that adding a new package to the program will automatically add new functionality to the program without having to modify code in other packages?
Here's a code sample which should illustrate what I'm trying to do.
src/say/say.go:
package main
import (
"os"
"reg"
)
func main() {
if len(os.Args) != 2 {
os.Stderr.WriteString("usage:\n say <what_to_say>\n")
os.Exit(1)
}
cmd, ok := reg.GetFunc(os.Args[1])
if ok {
os.Stdout.WriteString(cmd())
os.Stdout.Write([]byte{'\n'})
} else {
os.Stderr.WriteString("I can't say that!\n")
os.Exit(1)
}
}
src/reg/reg.go:
package reg
var registry = make(map[string]func() string)
func Register(name string, f func() string) {
registry[name] = f
}
func GetFunc(name string) (func() string, bool) {
f, ok := registry[name]
return f, ok
}
src/hi/hi.go:
package hi
import (
"reg"
}
func init() {
reg.Register("hi", func() string {
return "Hello there!"
})
}
When coding this up, I naively supposed that perhaps the package "hi" would be found by the go compiler and compiled into the binary. Then, at load time, the init() function would run. If that was how things worked, I'd have been able to drop in something like the following to add a new "say no" command:
src/no/no.go:
package no
import (
"reg"
)
func init() {
reg.Register("no", func() string {
return "Not a chance, bub."
})
}
But, it doesn't seem to work that way.
I may just be thinking about the problem too much through a Pythonic lens, but is there some way to accomplish something somewhat like what I'm shooting for? If not, I'll change my tack and I will have learned something new about the Go way of doing things.
Thanks in advance!
Since you must use import in order for the compiler add a package, my suggestion would be to do the following:
Instead of using multiple drop-in packages, you could have only one single package with multiple drop-in files. Each command file is placed in the same package folder (cmds). This is possible since you are allowed to have multiple init in a package, and you would not have to make any edits to say.go, no matter how many new drop-in files you add.
package main
import (
"os"
"reg"
_ "cmds"
)
....
And previous package no
// Command no
package cmds
import (
"reg"
)
func init() {
reg.Register("no", func() string {
return "Not a chance, bub."
})
}
Based on what I read about the init function, I think your example would work if you just added "hi" and "no" to the list of packages you are importing in say.go. Does it work if you do that?
I know you did not want to change the code in say.go, so I suppose that isn't really a solution.
Related
I'd like to achieve 100% test coverage in go code. I am not able to cover the following example - can anyone help me with that?
package example
import (
"io/ioutil"
"log"
)
func checkIfReadable(filename string) (string, error) {
_, err := ioutil.ReadFile(filename)
if err != nil {
log.Fatalf("Cannot read the file... how to add coverage test for this line ?!?")
}
return "", nil
}
func main() {
checkIfReadable("dummy.txt")
}
Some dumy test for that:
package example
import (
"fmt"
"testing"
)
func TestCheckIfReadable(t *testing.T) {
someResult, err := checkIfReadable("dummy.txt")
if len(someResult) > 0 {
fmt.Println("this will not print")
t.Fail()
}
if err != nil {
fmt.Println("this will not print")
t.Fail()
}
}
func TestMain(t *testing.T) {
...
}
The issue is that log.Fatalf calls os.Exit and go engine dies.
I could modify the code and replace built-in library with my own - what makes the tests less reliable.
I could modify the code and create a proxy and a wrapper and a .... in other words very complex mechanism to change all calls to log.Fatalf
I could stop using built-in log package... what is equal to asking "how much is go built-in worth?"
I could live with not having 100% coverage
I could replace log.Fataf with something else - but then what is the point for built-in log.Fatalf?
I can try to mangle with system memory and depending on my OS replace memory address for the function (...) so do something obscure and dirty
Any other ideas?
Use log.Print instead of log.Fatal and return the error value that you declared in signature of function checkIfReadable. Or don't the error it and return it to some place that knows better how to handle it.
The function log.Fatal is strictly for reporting your program's final breath.
Calling log.Fatal is a bit worse than calling panic (there is also log.panic), because it does not execute deferred calls. Remember, that overusing panic in Go is considered a bad style.
A good way to get 100% test coverage and not fail at the same time is to use recover() to catch the panic that is thrown by log.Fatalf().
Here are the docs for recover. I think it fits your use case nicely.
I'm just starting with Golang and I am very confused about interacting with other packages and using structs. Right now I am simply trying to return the a struct generated by a method in the gopsutil library. Specifically the return of the following function: enter link description here
My code for this is the following:
package main
import (
"fmt"
"github.com/shirou/gopsutil/cpu"
)
func main() {
cpu_times = getCpuTime()
fmt.Println(cpu_times)
}
func getCpuTime() TimesStat {
ct, _ := cpu.Times(false)
return ct
}
This returns TimesStat as undefined. I tried returning a few different syntactical variations, however the only return value I have found that compiles is interface{}, which gets me the struct inside of brackets (eg [{values...}]) and that led to some other problems. I can't seem to find any examples of what I am trying to do. Any help appreciated thanks.
you need to include the package name before the type, like so:
func getCpuTime() []cpu.TimesStat { // with package name before type
ct, _ := cpu.Times(false)
return ct
}
since that is a slice of cpu.TimesStat, you probably want to add an index in the calling function or change the function to just return a single cpu.TimesStat. (thanks to #algrebre)
I am trying to refactor some test code and in two packages I need to do the same thing (connect to a DB). I am getting an import cycle. I get why I can't do it, but am wondering what the best way around it is.
Some specifics, I have three packages: testutils, client, engine.
In engine I define an interface & implementation (both exported).
package engine
type interface QueryEngine {
// ...
}
type struct MagicEngine {
// ...
}
And then in the testutils package I will create a MagicEngine and try and return it.
package testutils
func CreateAndConnect() (*engine.MagicEngine, error) {
// ....
}
Now in the test code (using a TestMain) I need to do something like
package engine
func TestMain(m *testing.M) {
e, err := testutils.CreateAndConnect()
// ....
os.Exit(m.Run())
}
This is of course a cycle. I want to do this so that I can in the client package also use this testutils.CreateAndConnect() method. I don't want to repeat the code in both packages. I don't want it in the main code of the engine package, it is very specific to the tests.
I tried adding it as an exported method on the engine test class (engine/engine_test.go) and using it in the client/client_test.go. No dice. :/
I feel I have done this in other languages, but could be crazy. What is the best way to structure this code for reusability?
You could use black-box style testing because the components of engine are exported. Change your tests to be in package engine_test:
package engine_test
import "engine"
import "testutils"
func TestMain(m *testing.M) {
e, err := testutils.CreateAndConnect()
// ....
os.Exit(m.Run())
}
I am developing some tests for my code (using the testing package), and I am wondering what's the best way to mock functions inside the tested function:
Should I pass the function as parameter?
In that case, what if that function calls another function? Should I pass both the first and second function as parameters in the tested one?
Note: some of the functions are called on objects (i.e. someObj.Create()) and use HTTP API calls.
UPDATE for clarification:
Example: functions
func f1() error {
... //some API call
}
func (s *SomeStruct) f2() error {
return f1
}
func f3() error {
return nil
}
func f4() error {
...
err = obj.f2()
...
err = f3()
...
}
For the above: if I want to test f4, what's the best way to mock f2 and f3?
If I pass f2 and f3 to f4 as parameters it would work, but then what for the f2 test? Should I pass f1 to f2 as parameter?
And if that's it, should then f4 have f1 as well in the parameters?
As a general guideline, functions aren't very mockable so its in our best interests to mock structs that implement a certain interface that may be passed into functions to test the different branches of code. See below for a basic example.
package a
type DoSomethingInterface interface {
DoSomething() error
}
func DoSomething(a DoSomethingInterface) {
if err := a.DoSomething(); err != nil {
fmt.Println("error occurred")
return
}
fmt.Println("no error occurred")
return
}
package a_test
import (
"testing"
"<path to a>/a"
)
type simpleMock struct {
err error
}
func (m *simpleMock) DoSomething() error {
return m.err
}
func TestDoSomething(t *testing.T) {
errorMock := &simpleMock{errors.New("some error")}
a.DoSomething(errorMock)
// test that "an error occurred" is logged
regularMock := &simpleMock{}
a.DoSomething(regularMock)
// test "no error occurred" is logged
}
In the above example, you would test the DoSomething function and the branches that happens eg. you would create an instance of the mock with an error for one test case and create another instance of the mock without the error to test the other case. The respective cases are to test a certain string has been logged to standard out; in this case it would be "error occurred" when simpleMock is instantiated with an error and "no error occurred" when there simpleMock is not instantiated with an error.
This can of course be expanded to other cases eg. the DoSomething function actually returns some kind of value and you want to make an assertion on the value.
Edit:
I updated the code with the concern that the interface lives in another package. Note that the new updated code has a package a that contains the interface and the function under test and a package a_test that is merely a template of how to approach testing a.DoSomething.
I'm not sure what you're trying to do here but I'll explain how testing should be done in Go.
Lets say we have an application with the following directory hierarchy:
root/
pack1/
pack1.go
pack1_test.go
pack2/
pack2.go
pack2_test.go
main.go
main_test.go
We'll assume that pack2.go has the functions you want to test:
package pack2
func f1() error {
... //some API call
}
func (s *SomeStruct) f2() error {
return f1
}
func f3() error {
return nil
}
func f4() error {
...
err = obj.f2()
...
err = f3()
...
}
Looks good so far. Now if you want to test the functions in pack2, you would create a file called pack2_test.go. All test files in go are named similarly (packagename_test.go). Now lets see the inside of a typical test for a package (pack2_test.go in this example):
package pack2
import (
"testing"
"fmt"
)
TestF1(*testing.T) {
x := "something for testing"
f1() // This tests f1 from the package "pact2.go"
}
TestF2(*testing.T) {
y := new(somestruct)
y.f2() // tests f2 from package "pact2.go"
}
TestF3(*testing.T) {
/// some code
f3() // tests f3
}
TestF4(*testing.T) {
/// code
f3() // you get the gist
}
Let me explain. Notice how in pack2_test.go, the first line says that the package is pack2. In a nutshell, this means that we're in the "scope" of the package pack2 and thus all the functions found in pack2 can be called as if you're within pack2. Thats why, within the Testf* functions, we could've called the functions from pack2. Another thing to note is the imported package "testing". This helps with two things:
First, it provides some functionality for running tests. I won't go into that.
Second, it helps identify the functions that go test should run.
Now to the functions. Any function within a test package that has the prefix "Test" and the parameters "t *testing.T" (you can use "*testing.T" when you don't need to use the testing functionality) will be executed when you run go test. You use the variable t to reference the testing functionality I mentioned. You can also declare functions without the prefix and call them within the prefixed functions.
So, if I go to my terminal and run go test, it will execute the functions you want to test, specified in pack2_test.go
You can learn more about testing here and here
I want to parse and set a variable conditionally in Go at the global package level based on the value of of an ENV var, so that I don't have to check for it every time in a utility function (as the variable would be declared once at run time). For example, what I want to accomplish is (Go pseudo-code):
import (
"fmt"
"os"
"strconv"
)
// This works to read the value of MYVAR (=true/false)
var myvar string = os.Getenv("MYVAR")
// Apparently this is too much for Go
var myvarbool, _ = strconv.ParseBool(myvar)
// Utility function to check for value
func mycheck() {
if myvarbool {
fmt.Print("MYVAR is true")
}
}
This is a library package, so doesn't have a main() function to do this kind of setup, but I want to be able to use mycheck() in other functions in the library, and don't want to have to read and parse MYVAR every time mycheck() is called.
One way to accomplish what you're looking to do would be to process the environment variable in an init() function, so something like the following would work:
package main
import (
"fmt"
"os"
"strconv"
)
var (
myvar string = os.Getenv("MYVAR")
myvarbool bool
)
func init() {
myvarbool, _ = strconv.ParseBool(myvar)
}
// Utility function to check for value
func mycheck() {
if myvarbool {
fmt.Print("MYVAR is true")
}
}
Playground
I'm not sure what problem you had, your code from the OP is valid, also you can have it in one line like:
var myvarbool, _ = strconv.ParseBool(os.Getenv("MYVAR"))
playground
After actually thinking about this for a couple of minutes, I've realised I can just create a wrapper function to do the work, and call that instead (again, largely pseudo-code without extra checks):
var myenvbool bool = myenv("MYVAR")
func myenv(envvar string) bool {
myenvvalue := os.Getenv(envvar)
myenvbool, _ := strconv.ParseBool(myenvvalue)
return myenvbool
}
func checkenv() {
if myenvbool {
fmt.Print("myenvbool is true")
}
}