How to handle error of singleton only once?
I have a singleton service which could generate error only at first call and then it returns already created instance.
Service looks like below:
package data
import (
"sync"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var (
databaseSingleton *gorm.DB
once sync.Once
)
func NewDatabase() (*gorm.DB, error) {
once.Do(func() {
// ...
databaseSingleton, err = gorm.Open(postgres.Open(connectionString), config)
if err != nil {
return nil, err
}
})
return databaseSingleton, nil
}
The problem is multiple services which uses databaseSingleton above handle error which can occurs only once.
Services which uses databaseSingleton looks like below:
func NewServiceOne() (ServiceOne, error) {
database, err := NewDatabase()
// want omit this error handling
if err != nil {
return nil, err
}
return &serviceOne{database}, nil
}
func NewServiceTwo() (ServiceTwo, error) {
database, err := NewDatabase()
// want omit this error handling
if err != nil {
return nil, err
}
return &serviceTwo{database}, nil
}
func NewServiceThree() (ServiceThree, error) {
database, err := NewDatabase()
// want omit this error handling
if err != nil {
return nil, err
}
return &serviceThree{database}, nil
}
If there any way to omit this error handling because err could be generated only once?
If the error occurs (only once), your databaseSingleton will not be setup. You should return the error in all cases.
Although this isn't something you can do anything about (since the attempt to initialize databaseSingleton will not be repeated due to the use of sync.Once), you could as well halt the app.
In fact, there is no point deferring this initialization, you could just do it during package init, and terminate if it fails. And if it succeeds, you could use databaseSingleton without having to check error of the initialization.
So simply do it like this:
var databaseSingleton *gorm.DB
func init() {
var err error
databaseSingleton, err = gorm.Open(postgres.Open(connectionString), config)
if err != nil {
log.Fatalf("Failed to connect to DB: %v", err)
}
}
NewServiceOne() could look like this:
func NewServiceOne() ServiceOne {
return &serviceOne{databaseSingleton}
}
Related
I am writing an API using go-fiber, and I want to check, if passed JSON conforms an interface that I want to see. So I decided to use 1.18's feature - generics. Here is what I did, but it does not work due to type problem.
func checkDataConformsInterface[I any](format I, c *fiber.Ctx) (I, error) {
if err := c.BodyParser(&format); err != nil {
return nil, err
}
return c.JSON(format), nil
}
The errors say
src/endpoints/v1/tasks.go:36:10: cannot use nil as I value in return statement
src/endpoints/v1/tasks.go:39:9: cannot use c.JSON(format) (value of type error) as type I in return statement
And I want to call the function like this:
type CreateTaskDF struct {
Target string `json:"target"`
Deepness int `json:"deepness"`
}
func CreateTask(c *fiber.Ctx) error {
data, err := checkDataConformsInterface[CreateTaskDF](&CreateTaskDF{}, c)
if err != nil {
log.Fatal(err)
}
// work with data here
...
How should I convert the return value in the function to make it work? Thanks!
It probably could work like this(if you do not consider any lib-based payload validators, which exist in almost every golang routing lib or web framework). So, to just validate your data you can use this:
func checkDataConformsInterface[I any](format I, c *fiber.Ctx) bool {
if err := c.BodyParser(&format); err != nil {
return false
}
return true
}
So I came up with the following solution
func checkDataConformsInterface[I any](format *I, c *fiber.Ctx) error {
if err := c.BodyParser(&format); err != nil {
return err
}
err := c.JSON(format)
if err != nil {
return err
}
return nil
}
which can be called like
func CreateTask(c *fiber.Ctx) error {
parsedData := CreateTaskDF{}
err := checkDataConformsInterface[CreateTaskDF](&parsedData, c)
if err != nil {
c.SendStatus(400)
return c.SendString("Wrong data")
}
Please, point me the problems if any
I am trying to mock the below method using gomock
func GetS(tenantName string) (*mgo.Session, error) {
ctx := apiContext.TContext{}
url, err := connectionURLList.get(tenantName)
if err != nil {
log.GenericWarning(ctx,
fmt.Sprintf("connection to %s not yet created, creating one: %v", tenantName, err), nil)
if err := connectMongo(tenantName); err == nil {
return GetS(tenantName) //singleton recursion to again call GetS
}
return nil, err
}
// ignoring error, expected we will always setting session in session map
session, _ := connectionList.get(url)
return session.Copy(), err
}
My Interface
type MongoManager interface {
GetS(tenantName string)
}
func TestGetS(t *testing.T) {
//var mgoCall *mgo.Session
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockManagerObj := mocks.NewMockMongoManager(mockCtrl)
mockManagerObj.EXPECT().GetS("cacargroup").Return(nil)
}
I am Getting the below error . Can someone help
$ go test
--- FAIL: TestGetS (0.00s)
mongoManager_test.go:20: missing call(s) to *mocks.MockMongoManager.GetS(is equal to cacargroup) /Users/charles/workspace/src/bitbucket.org/tekion/tbaas/mongoManager/mongoManager_test.go:16
mongoManager_test.go:20: aborting test due to missing call(s) FAIL exit status 1
You see actually the method in your interface implemented with return type of an error. But you are using like it returns nothing and chaining the implementation. Just remove the return type of GetS.
type fn func(string) (*mgo.Session, error)
type MongoManager interface {
NewFunction(GetS, "cascade")
}
func TestGetS(t *testing.T) {
//var mgoCall *mgo.Session
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockManagerObj := mocks.NewMockMongoManager(mockCtrl)
mockManagerObj.EXPECT().GetS("cacargroup").Return(nil)
}
Also you have to remove it from GetS function too
func NewFunction(GetS fn, value string){
GetS("cascade")
}
func GetS(tenantName string) (*mgo.Session, error){
ctx := apiContext.TContext{}
url, err := connectionURLList.get(tenantName)
if err != nil {
log.GenericWarning(ctx,
fmt.Sprintf("connection to %s not yet created, creating one: %v", tenantName, err), nil)
if err := connectMongo(tenantName); err == nil {
return GetS(tenantName) //singleton recursion to again call GetS
}
return nil, err
}
// ignoring error, expected we will always setting session in session map
session, _ := connectionList.get(url)
}
Here's a little golang program that connects to localhost via ssh and does some sftp-like operations. It works fine but I would like to make the doTheWork() function more testable.
I've read up on using gomock to create mocks for interfaces. So I've already created two interfaces, Sftper and Walker, and I'm using those instead of calling sftp's methods directly.
What I want to do is mock out all the stuff from stfp inside doTheWork(). Close() is easy because it only returns an error (or nil). But Create() returns *sftp.File and that is a struct, not an interface. Same with Walk() which returns another struct. I would have to create one of these structs in my test code, and that's not really possible without actually calling the real sftp library and making a remote connection and creating a remote file, and this is exactly what I am trying to mock out.
What am I missing?
package main
import (
"log"
"os"
"github.com/kr/fs"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
// Sftper helps make things testable
type Sftper interface {
Close() error
Create(path string) (*sftp.File, error)
Lstat(p string) (os.FileInfo, error)
Walk(root string) *fs.Walker
}
// Walker helps make things testable
type Walker interface {
Step() bool
Err() error
Path() string
}
var sftpclient Sftper
var w Walker
func doTheWork(sftpclient Sftper) {
defer sftpclient.Close()
// walk a directory
w = sftpclient.Walk("/tmp/")
for w.Step() {
if w.Err() != nil {
continue
}
log.Println(w.Path())
}
// leave your mark
f, err := sftpclient.Create("hello.txt")
if err != nil {
log.Fatal(err)
}
if _, err1 := f.Write([]byte("Hello world!")); err1 != nil {
log.Fatal(err1)
}
// check it's there
fi, err := sftpclient.Lstat("hello.txt")
if err != nil {
log.Fatal(err)
}
log.Println(fi)
}
func main() {
config := &ssh.ClientConfig{
User: "myusername",
Auth: []ssh.AuthMethod{
ssh.Password("mysupersecretpassword"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
conn, err0 := ssh.Dial("tcp", "localhost:22", config)
if err0 != nil {
log.Fatal("Failed to dial: ", err0)
}
// open an SFTP session over an existing ssh connection.
var err error
sftpclient, err = sftp.NewClient(conn)
if err != nil {
log.Fatal(err)
}
doTheWork(sftpclient)
}
func getHostname() (string, error) {
host, err := os.Hostname()
if err != nil {
// Untested code-block
return "", err
}
// Don't judge the example. This is just an example.
// Basically, I want to know how to induce error in os.Hostname()
return host, nil
}
The corresponding test for this function is:-
import "testing"
func TestGetHostname(t *testing.T) {
host, err := getHostname()
if err != nil {
t.Errorf("Error executing getHostname(): %s", err)
}
if len(host) < 1 {
t.Errorf("Hostname returned is not proper")
}
}
If I were to make the 100% coverage for this function, I would like induce error in os.Hostname() so that I can run that if block too. How can I achieve that?
Is creating an interface and passing it as an argument to function the only way?
You can make the code testable by using dependency injection like this:
// Override for testing
var osHostname = os.Hostname
func getHostname() (string, error) {
host, err := osHostname()
if err != nil {
return "", err
}
return host, nil
}
In your test, you can replace the stub with an error-producing version.
func TestGetHostnameFails(t *testing.T) {
defer func() { osHostname = os.Hostname }()
osHostname = func()(string, error) { return "", errors.New("fail") }
got, err := getHostname()
if err == nil {
t.Errorf("getHostname() = (%v, nil), want error", got)
}
}
There's advantages and disadvantages to using package globals to do dependency injection. The advantages are that it's very simple, and most importantly to me doesn't mess the production code up too much. For those reasons it's my choice when you want to test code in isolation like this. The disadvantages are that you may forget to reset the state in your test, and it doesn't give a good testing api -- so if there's lots of tests that are using this stub (or worse, tests in another package), you may prefer to make the configuration explicit by putting it in a struct, and making getHostname a method of that struct.
This is just an example, but I feel compelled to provide a cautionary message about getting 100% test coverage. os.Hostname() is very unlikely to fail in practice, and it's also very unlikely that your error handling when it fails is wrong. It's much more likely that any dependency injection introduces a bug than it is that this test identifies any real bug.
what happens if your function under test panics?
add panic-checking to your testing code:
package hostname
import "testing"
func TestGetHostname(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("The code did panic")
}
}()
name, err := getHostname()
if err != nil {
t.Errorf("err: %v\n", err)
}
if len(name) == 0 {
t.Errorf("Hostname is empty")
}
}
old:
look inside os.hostname():
in case of err, it will returns:
return "", NewSyscallError("...", err)
your getHostname() repeats this again.
let me do it again (just to clarify what i'm talking about):
func getHostname2() (string, error) {
host, err := getHostname() // your getHostname !
if err != nil {
return "", err
}
return host, nil
}
this is extra superfluous, isn't it?
so i think this is enough:
package main
import "os"
import "fmt"
func main() {
name, err := os.Hostname()
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
fmt.Println(name)
}
so simple error checking is enough (like this):
package main
import "os"
import "fmt"
func work() {
if name, err := os.Hostname(); err != nil {
fmt.Printf("err: %v\n", err)
return
} else {
// do some job ...
fmt.Println(name)
}
}
func main() {
work()
}
i hope this helps.
I was performing some RPC tests, and stumbled across a problem I can't seem to solve. In my testing I create three separate RPC servers, all of which I try to close and shutdown. However upon performing my last test (TestRpcCodecServerClientComm), it seems my client connection is connecting to the first RPC server I started (I know this because I at some point attached IDs to the RPCHandlers), even though I attempted everything I could to make sure it was shutdown. Though the code is not there I have attempted to inspect every single error I could, but that did not bring about anything.
rpc.go
package rbot
import (
"io"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
func RpcCodecClientWithPort(port string) (rpc.ClientCodec, error) {
conn, err := net.Dial("tcp", "localhost:"+port)
if err != nil {
return nil, err
}
return jsonrpc.NewClientCodec(conn), nil
}
func RpcCodecServer(conn io.ReadWriteCloser) rpc.ServerCodec {
return jsonrpc.NewServerCodec(conn)
}
rpc_test.go
package rbot
import (
"errors"
"fmt"
"net"
"net/rpc"
"testing"
)
type RPCHandler struct {
RPCServer net.Listener
conn rpc.ServerCodec
done chan bool
TestPort string
stop bool
GotRPC bool
}
func (r *RPCHandler) SetupTest() {
r.stop = false
r.GotRPC = false
r.done = make(chan bool)
r.TestPort = "5556"
}
// TODO: Create separate function to handle erroring
func (r *RPCHandler) CreateRPCServer() error {
rpc.RegisterName("TestMaster", TestAPI{r})
var err error
r.RPCServer, err = net.Listen("tcp", ":"+r.TestPort)
if err != nil {
return err
}
go func() {
for {
conn, err := r.RPCServer.Accept()
if err != nil || r.stop {
r.done <- true
return
}
r.conn = RpcCodecServer(conn)
rpc.ServeCodec(r.conn)
}
}()
return nil
}
func (r *RPCHandler) CloseRPCServer() error {
r.stop = true
if r.conn != nil {
err := r.conn.Close()
if err != nil {
fmt.Println(err)
}
}
err := r.RPCServer.Close()
<-r.done
return err
}
type TestAPI struct {
t *RPCHandler
}
func (tapi TestAPI) Send(msg string, result *string) error {
if msg == "Got RPC?" {
tapi.t.GotRPC = true
return nil
}
return errors.New("Didn't receive right message")
}
// Check if we can create and close an RPC server successfully using the RPC server codec.
func TestRpcCodecServer(t *testing.T) {
r := RPCHandler{}
r.SetupTest()
err := r.CreateRPCServer()
if err != nil {
t.Fatalf("Could not create rpc server! %s:", err.Error())
}
err = r.CloseRPCServer()
if err != nil {
t.Fatalf("Could not close RPC server! %s:", err.Error())
}
}
// Check if we can create a client without erroring.
func TestRpcCodecClientWithPortt(t *testing.T) {
r := RPCHandler{}
r.SetupTest()
r.CreateRPCServer()
defer r.CloseRPCServer()
RPCClient, err := RpcCodecClientWithPort(r.TestPort)
defer RPCClient.Close()
if err != nil {
t.Fatalf("Could not create an RPC client! %s:", err.Error())
}
}
// Let's double check and make sure our server and client can speak to each other
func TestRpcCodecServerClientComm(t *testing.T) {
r := RPCHandler{}
r.SetupTest()
r.CreateRPCServer()
defer r.CloseRPCServer()
RPCCodec, _ := RpcCodecClientWithPort(r.TestPort)
RPCClient := rpc.NewClientWithCodec(RPCCodec)
defer RPCClient.Close()
var result string
err := RPCClient.Call("TestMaster.Send", "Got RPC?", &result)
if err != nil {
t.Fatalf("Error while trying to send RPC message: %s", err.Error())
}
if !r.GotRPC {
t.Fatalf("Could not send correct message over RPC")
}
}
Not sure if I'm just mishandling the connection or something of the like, any help would be much appreciated.
For the Record The RPC api does receive the correct string message
While not the source of your problems, your test configuration has a few race conditions which you should take care of before they cause problems. Always check for issues with the -race option. You should also let the OS allocate the port so you don't run into conflicts. See for example how httptest.Server works.
Your failure here is that you're not creating a new rpc.Server for each test, you're reusing the rpc.DefaultServer. The first call to CreateRPCServer registers a TestAPI under the name TestMaster. Each subsequent call uses the already registered instance.
If you create a new rpc.Server each time you setup the test and register a new TestAPI, the final test will pass.
srv := rpc.NewServer()
srv.RegisterName("TestMaster", testAPI)
...
// and then use srv to handle the new connection
srv.ServeCodec(RpcCodecServer(conn))