-4

I have a package "tools" that expose a method that must execute some logic only one time. The next calls don't do nothing.

The file tools/tools.go is like:

package tools

import (
    "fmt"
    "sync"
)

var writed = false
var mutex = sync.Mutex{}

func OnlyOneWrite() {
    mutex.Lock()
    defer mutex.Unlock()

    if writed {
        return
    }
    writed = true
    // some stuff that must happen only once
    fmt.Println("I must be printed only one time")
}

Next i have a test inside the A package. The file a/a_test.go looks like:

package a_test

import (
    "testing"

    "lock_example/tools"
)

func TestA(t *testing.T) {
    const arbitraryGoRoutines = 30
    for i := 0; i < arbitraryGoRoutines; i++ {
        tools.OnlyOneWrite()
    }
}

When i run the test:

go test -v ./a/...
=== RUN   TestA
I must be printed only one time
--- PASS: TestA (0.00s)
PASS
ok      lock_example/a  0.002s

Perfect, only one print.

BUT, i have another package with the same logic b/b_test.go as follow:

package b

import (
    "testing"

    "lock_example/tools"
)

func TestB(t *testing.T) {
    const arbitraryGoRoutines = 30
    for i := 0; i < arbitraryGoRoutines; i++ {
        tools.OnlyOneWrite()
    }
}

When i run the two tests:

go test -v ./...  
=== RUN   TestA
I must be printed only one time
--- PASS: TestA (0.00s)
PASS
ok      lock_example/a  (cached)
=== RUN   TestB
I must be printed only one time
--- PASS: TestB (0.00s)
PASS
ok      lock_example/b  0.002s
?       lock_example/tools      [no test files]

Happen two prints!

(also, i try with sync.Once, but the problem persists)

Questions

  • When i run the tools.OnlyOneWrite() i understand that a new variable writed is created per package, two that start in false. Is this true?
  • What alternatives i have? I been thinking in a lock with a file, but the situation is similar, can happen that the os.Create is called at the same time by the package A an B printing again two times

Use case of the real world

I been creating some integration tests, but i need to run a setup in the database (some CREATE TABLE, INSERTS...). So i create this package "tools" that makes that.

The package A refers to the integration tests of some process of my API, same for B. Twice call this func of the package "tools"

And the problem is A and B call the method that interact with the DB twice creating errors of "table already exists" (or if i surpass that, a duplicated primary key)

2
  • Before solving an XY problem, consider using CREATE TABLE IF NOT EXISTS so it is created only once, as well as declaring columns as a PRIMARY KEY so no duplicates can be inserted, etc. You may also benefit from using TRANSACTION and ROLLBACK... Commented Oct 10 at 0:54
  • Yea, but this creates aren't mine and i can't modify that Commented Oct 10 at 1:02

1 Answer 1

2

From the Go docs:

Go test runs in two different modes:

The first, called local directory mode, occurs when go test is invoked with no package arguments (for example, 'go test' or 'go test -v'). In this mode, go test compiles the package sources and tests found in the current directory and then runs the resulting test binary. In this mode, caching (discussed below) is disabled. After the package test finishes, go test prints a summary line showing the test status ('ok' or 'FAIL'), package name, and elapsed time.

The second, called package list mode, occurs when go test is invoked with explicit package arguments (for example 'go test math', 'go test ./...', and even 'go test .'). In this mode, go test compiles and tests each of the packages listed on the command line. If a package test passes, go test prints only the final 'ok' summary line. If a package test fails, go test prints the full test output. If invoked with the -bench or -v flag, go test prints the full output even for passing package tests, in order to display the requested benchmark results or verbose logging. After the package tests for all of the listed packages finish, and their output is printed, go test prints a final 'FAIL' status if any package test has failed.

You run go test -v ./... so this "compiles and tests each of the packages listed on the command line" (i.e. compiles and runs test for package a, then compiles and runs test for package b). The tests run seperatly, so OnlyOneWrite is only called once in each test executable.

When i run the tools.OnlyOneWrite() i understand that a new variable writed is created per package, two that start in false. Is this true?

Well the variable is global, it's set to true when OnlyOneWrite is called. Your approach is OK, but using global variables like this can lead to bugs that are hard to track down. I am a bit confused about how this fits into a full system, based on what you have shown restarting the final application would result in the same issue (unless you have other checks).

What alternatives i have? I been thinking in a lock with a file, but the situation is similar, can happen that the os.Create is called at the same time by the package A an B printing again two times

As you said "creates aren't mine and i can't modify that" your options are a somewhat limited, possibilities may include:

  • Restructure the app so table creation is triggered independently of the packages (and run in advance of your tests).
  • Consider adding a table that records migrations (the approach used by things like golang-migrate).
  • Spin up a separate database for each test (dockertest is one approach).
  • Instead of using a global variable, check if the table exists (assuming you can access the database).
  • Just call OnlyOneWrite but check the error retrurned (ignoring it if the problem was that the table was already there).
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks!, i finally go with the first option (using testMain in the root package of the integration tests)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.