Golang: tests and working directory
Asked Answered
M

14

98

I'm writing some unit tests for my application in Go. The tests fail however because it cannot find the configuration files. Normally the binary looks for the configuration files in the working directory under the path conf/*.conf.

I figured that browsing to the directory that has conf/ and running go test in it would solve it, but it still reports that the file system cannot find the path specified.

How can I tell go test to use a certain directory as the working directory so that the tests may actually be executed?

Milline answered 24/5, 2014 at 15:58 Comment(1)
If your unit tests depend on configuration, they aren't unit tests.Convoluted
F
62

You may be able to use the Caller to get the path to the current test source file, like this:

package sample

import (
    "testing"
    "runtime"
    "fmt"
)

func TestGetFilename(t *testing.T) {
    _, filename, _, _ := runtime.Caller(0)
    t.Logf("Current test filename: %s", filename)
}
Flavescent answered 28/1, 2015 at 19:5 Comment(2)
Agreed, this is a very nice way to to do it. Combine with the Dir function from package "path/filepath" to build a path relative the input go file's folder.Sardonyx
Note that Caller retuns the package name if the tests are run using go test github.com/the/package. I'm trying to package a go application for nixpkgs and this is giving me some trouble.Retch
I
31

I do not believe this is possible. I have not been able to find documentation stating this explicitly, but I believe go test always uses the package directory (containing the go source files) as the working directory.

Iover answered 24/5, 2014 at 16:43 Comment(2)
Thanks, that's very annoying but I suppose there's no way around it. I'll probably add a bug report/suggestion to the Go project to perhaps add a -w $d or --working-dir=$d parameter to go test.Milline
There should absolutely be some documentation here -- hopefully it's actually a guarantee :).Cottage
C
22

As a workaround, I compiled the test and execute the test from the current directory.

go test -c && ./<mypackage>.test

Or, if you want a generic command that you can use, you can rename the test file with -o option.

go test -c -o xyz.test && ./xyz.test

Courtneycourtrai answered 9/4, 2015 at 14:24 Comment(2)
This is the best answer for me! I could run my tests from the same dir where my binary usually runs from.Andi
This works, does anyone know how to integrate it with vscode?Interlaminate
D
17

No matter where the work directory is. It must be under your project Dir. So my solution is

wd, _ := os.Getwd()
for !strings.HasSuffix(wd, "<yourProjectDirName>") {
    wd = filepath.Dir(wd)
}

raw, err := ioutil.ReadFile(fmt.Sprintf("%s/src/conf/conf.dev.json", wd))

Your path should always start from your project Dir. Every time you read the file in a package and accessed by main.go or your another package unit test. It will always work.

Dday answered 13/12, 2017 at 4:22 Comment(2)
This is very nice, but won't work if you have the (pretty common) pattern of using pkgname/cmd/pkgname.Reverend
You can try smaller wd = strings.SplitAfter(wd, "<yourProjectDirName>")[0]Cle
S
13

While not really convenient, you can always pass it as a command line variable, for example :

package blah_test

import (
    "flag"
    "fmt"
    "os"
    "testing"
)

var (
    cwd_arg = flag.String("cwd", "", "set cwd")
)

func init() {
    flag.Parse()
    if *cwd_arg != "" {
        if err := os.Chdir(*cwd_arg); err != nil {
            fmt.Println("Chdir error:", err)
        }
    }
}

func TestBlah(t *testing.T) {
    t.Errorf("cwd: %+q", *cwd_arg)
}

Then run it like :

┌─ oneofone@Oa [/tmp]                                                                                             
└──➜ go test . -cwd="$PWD"
--- FAIL: TestBlah (0.00 seconds)
        blah_test.go:16: cwd: "/tmp"
Sandisandidge answered 24/5, 2014 at 20:25 Comment(2)
That's a good idea. I'm curious though, what command prompt is that you're using?Milline
I'm using fish, the specific function to show the prompt is @ github.com/OneOfOne/etc-fish/blob/master/functions/…Sandisandidge
E
11

To add init function into *_test.go under your test package. Test package will run this function before test function start.

func init() {
    _, filename, _, _ := runtime.Caller(0)
    // The ".." may change depending on you folder structure
    dir := path.Join(path.Dir(filename), "..")
    err := os.Chdir(dir)
    if err != nil {
        panic(err)
    }  
}
Ethnomusicology answered 17/2, 2020 at 8:42 Comment(2)
I had assumed that go ran the tests in the same directory as the source code being tested, but at least as of go version 1.15.5, the tests are compiled in a temporary directory under /tmp (e.g. /tmp/go-build549268541) and with a working directory of /tmp. So os.Chdir("..") will not work anymore. the answer using runtime.Caller(0) seems like the way to go.Vagrant
I like to add it in a testing package, and just import it like "_ yourmod/testing" for all testsHeidyheifer
D
10

You can use the os package.

You would want to do something like this

    func TestMyFunction(t *testing.T) {
        os.Chdir("./path")

        //TEST FUNCTION

        os.Chdir("..")
    }

There are several possibilities in the os package.

Dunton answered 27/4, 2016 at 14:57 Comment(1)
Unfortunately, this does not seem to work when the source code contains *template.Must(...) calls to compile HTML templates.Lardon
N
4

I currently use a neat solution for this problem, instead of opening the file directly by calling os.Open(), I use the embed package in a smart way:

First I create a global variable in my root package called:

//go:embed config/* otherdirectories/*
var RootFS embed.FS

Then I just open the files inside my tests by using this global variable, e.g.:

func TestOpenConfig(t *testing.T) {
    configFile, err := rootpkg.RootFS.ReadFile("config/env")
    if err != nil {
        t.Fatalf("unable to open config/env file: %s", err)
    }

    if string(configFile) != "FOO=bar\n" {
        t.Fatalf("config file contents differ from expected: %s", string(configFile))
    }
}

This is a neat trick because now you can always work with relative paths from your root package, which is what I used to do in other programming languages.

Of course, this has the restriction that you will need to import your root package, which depending on your package layout might not be ideal because of cyclic imports. If this is your case you might just create a embed.go file inside the config directory itself.

One other drawback is that you are embedding test files in your binary, this is probably ok if your test files are not very big, like megabytes big, so I don't really mind this issue.

I also created a repository for illustrating this solution:

https://github.com/VinGarcia/golang-reading-files-from-tests

Nephritic answered 15/3, 2022 at 20:28 Comment(0)
O
3

Go 1.20 is getting new -C arguments for "go subcommands" so this should help:

go test -C directory/ ...
Orpine answered 8/12, 2022 at 13:3 Comment(0)
I
2

I've had a similar problem and found the solution on this blog

Basically you can change the folder that the test is running using a similar function:

package main

import (
    "os"
    "path"
    "runtime"
)

func MakeFunctionRunOnRootFolder() {
    _, filename, _, _ := runtime.Caller(0)
    // The ".." may change depending on you folder structure
    dir := path.Join(path.Dir(filename), "..")
    err := os.Chdir(dir)
    if err != nil {
        panic(err)
    }
}
Illicit answered 10/10, 2019 at 8:11 Comment(0)
C
2

I know this is an old question but I had the same problem trying to use migrations for the database on my tests, and maybe this solution helps someone.

Since there is no native way of getting the project directory, you could identify some file or directory that you know it's only in the root of the project (in my case, it was the relative directory database/migrations). Once you have this unique relative directory, you could have a function like the following to obtain the project root directory. It just gets the current working directory (assuming it's inside the project's directory) and starts to navigate all the way up until it finds a dir that has the relative directory you know it's on the root of the project:

func FindMyRootDir() string {
    workingDirectory, err := os.Getwd()

    if err != nil {
        panic(err)
    }

    lastDir := workingDirectory
    myUniqueRelativePath := "database/migrations"

    for {
        currentPath := fmt.Sprintf("%s/%s", lastDir, myUniqueRelativePath)

        fi, err := os.Stat(currentPath)

        if err == nil {
            switch mode := fi.Mode(); {
            case mode.IsDir():
                return currentPath
            }
        }

        newDir := filepath.Dir(lastDir)

        // Ooops, we couldn't find the root dir. Check that your "myUniqueRelativePath" really exists

        if newDir == "/" || newDir == lastDir {
            return ""
        }

        lastDir = newDir
    }
}

Of course it's not the most beautiful solution, but it works.

Clearway answered 23/3, 2020 at 18:25 Comment(1)
You can check for the .git folder which almost always means parent folderCle
M
0

It's a common practice in Go to place test fixtures in same package inside testdata folder.

Some examples from standard library:

Also, there is a post from Dave Cheney, where he suggests following code:

f, err := os.Open("testdata/somefixture.json")
Meed answered 13/8, 2021 at 22:35 Comment(0)
C
0

Here is function to find a root project directory that contains .git folder:

import (
    "os"
    "path/filepath"
)

func FindProjectDir() string {
    // Test can be started from command line in project folder or in IDE
    // Try to detect a correct path to a project directory

    // Find a project dir that contains .git
    curDir, _ := os.Getwd()
    projectDir := curDir
    for {
        _, err := os.Stat(projectDir + "/.git")
        if err == nil {
            return projectDir
        }
        projectDir = filepath.Dir(projectDir)
        if projectDir == "/" { // reached root
            return ""
        }
    }
}

You can adapt it and append the file name that you are looking for. Still I think this answer is better https://mcmap.net/q/216805/-golang-tests-and-working-directory But sometime you need also to execute some shell script in the project root folder so you need to know the parent folder.

Cle answered 5/12, 2023 at 13:10 Comment(0)
P
-1

I would use an Environment Variable for the location of your application. It seems to be the best way when running go tools, as test programs can be run from a temporary location.

// get home dir of app, use MYAPPHOME env var if present, else executable dir.
func exeDir() string {
    dir, exists := os.LookupEnv("MYAPPHOME")
    if exists {
        return dir
    } else {
        ex, err := os.Executable()
        if err != nil {
            panic(err)
        }
        exPath := path.Dir(ex)
        return exPath
    }
}
Phantasy answered 20/2, 2017 at 21:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.