I am using golang revel web framework and
I am trying to create a sqlite db in the current working directory.
model.go
func New(dbName string,table string) *Db {
_,filename,_,_ := runtime.Caller(1)
db , err := sql.Open("sqlite3",path.Join(path.Dir(filename),dbName))
if err != nil {
log.Fatal(err)
}
err = db.Ping()
if err != nil {
log.Panic(err)
}
database := &Db{Database:db}
_,err = db.Exec("create table %s" +
"( id integer primary key, " +
"name varchar(100),"+
"email varchar(100),"+
"branch varchar(100),"+
"help varchar(100)",)
if err != nil {
log.Fatal(err)
}
}
I have a test in place which just calls this function.
whenever i run the test using revel test or by going to the localhost:9000/#tests, the function Panics and the error message is
cannot open the database file.
The reason that is happening is because the filename returned by runtime.Caller(1) is /usr/local/go/src/runtime/asm_amd64.s for which the program has no permission.
if i directly write ./foo.db, even then the error shows.
I tried os.Getwd() which return empty string.
I also tried filepath.Abs(filepath.Dir(os.Args[0]))
but that returned /home/girish/GoProjects/bin/revel.d which is the revel binary.
So whats the best way to find the directory of the model.go?
It doesn't make sense to get the directory of the model.go file at runtime, because the compiled executable could be on a completely different filesystem.
You may want to get the directory of where the running executable was started from:
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
dir will be the folder where the program lives at runtime.
Related
I'm trying to execute .ics file that my program just created. Basically, my program is simple CLI calendar app, which generates .ics file. It would be nice if my program would execute this file and add it straight to OS calendar app, without unnecessary searching and executing through OS GUI.
I paste main function to better understanding.
func main() {
serialized, name := cal()
f, err := os.Create(name + ".ics")
if err != nil {
log.Fatal(err)
}
defer f.Close()
_, err2 := f.WriteString(serialized)
if err2 != nil {
log.Fatal(err2)
}
cmd := exec.Command(name + ".ics")
err = cmd.Run()
if err != nil {
log.Fatal(err)
}
}
As it's shown I tried with exec.Command, but it doesnt' work. I was even trying with additional prefixes like ./ or ~/, but it as well didn't work.
Error messages:
fork/exec ./Meeting.ics: permission denied
exec: "Meeting.ics": executable file not found in $PATH
So to sum it up - I want to skip the process where the user has to find a file and open it. I want to make it automatically as a part of my application.
Here's my repository if it would help https://github.com/lenoopaleno/golang-calendar
I'm working on WSL 2, Ubuntu 22.04
Beside the comments above, you might have a problem in your code with the defer f.Close()
The defer runs when the function ends. Until that time your file might or might not be closed and accessible by a different process.
Second you will most likely have to set an execute flag on the a program to run under unix style operating systems.
Program adjustment:
func main() {
serialized, name := cal()
f, err := os.Create(name + ".ics")
if err != nil {
log.Fatal(err)
}
_, err2 := f.WriteString(serialized)
if err2 != nil {
log.Fatal(err2)
}
f.Sync()
f.Close()
exec.Command(`chmod +x `+name+".ics").Run() // This can be done prettier
cmd := exec.Command(name + ".ics")
err = cmd.Run()
if err != nil {
log.Fatal(err)
}
}
I have a cli that I am making that is more for learning purposes and creating my own cli, that does stuff. Anyways, I am testing the delete function and it works fine and gives me the right answer. However, I don't believe that it is the best practice and was wondering if you could let me know if it's ok, or not.
Test file
func TestDeleteConfig(t *testing.T) {
err := cm.DeleteConfig()
if err != nil {
t.Errorf("error when deleting the folder: %s", err)
}
usr, err := user.Current()
if err != nil {
t.Errorf("error when getting user current: %s", err)
}
fp := filepath.Join(usr.HomeDir, ".config", "godot", "config.json")
fmt.Println("the path of the config file", fp)
if _, e := os.Stat(fp); !os.IsNotExist(e) {
t.Errorf("error path still exists: %v", e)
}
}
function being tested
func DeleteConfig() error {
usr, err := user.Current()
if err != nil {
return err
}
err = os.RemoveAll(filepath.Join(usr.HomeDir, ".config", "godot"))
if err != nil {
return err
}
return nil
}
The problem is that I don't want the DeleteConfig() to take any arguments as this is a hard path. I have a separate function for deleting a single file in which the help with this will solve that as well func DeleteFile(p string) error {}.
So for testing purposes should I just create a foo directory at a separate path (within the test), delete said directory, and assume that if it works on foo path then it should work with the godot directory?
It's a bit philosophical.
If you absolutely do not want to stub/mock any part of your code to replace accessing a real file system to do testing, then we're talking about what you'd call system or integration testing. That's fine in itself: for instance, you could run such tests in a throw-away container as part of a CI pipeline.
But with this approach you can't sensibly do what is called unit-testing.
To unit-test your function you need to replace something it uses with something "virtualized". Exactly how to do that is an open question.
Approach: make is possible to override os.RemoveAll
For instance, you can have a private global variable containing the "remove whole directory" function—like this:
var removeAll = os.RemoveAll
func DeleteConfig() error {
usr, err := user.Current()
if err != nil {
return err
}
err = removeAll(filepath.Join(usr.HomeDir, ".config", "godot"))
if err != nil {
return err
}
return nil
}
Then to test it you'd just monkey-patch the function with your own implementation which would have the same signature as os.RemoveAll, like this:
func TestDeleteConfig(t *testing.T) {
var actualPath string
removeAll = func(path string) {
actualPath = path
return nil
}
defer func() { removeAll = os.RemoveAll }()
DeleteConfig()
if actualPath != expectedPath {
t.Errorf("unexpected path: want %s, got %s", expectedPath, actualPath)
}
}
This approach also allows to test how your function handles errors: just replace it with something which generates an error and then afterwards check that your function under test returned that error (or wrapped it in an expected way, whatever).
Still, it's a bit low-tech for a number of reasons:
A global variable is involved, so you have to be sure no two tests which monkey-patch it run concurrently, or that all patching is done before running those tests.
If different tests need to set it to different value, they must be serialized.
Approach: do not hard-code the concept of "the user" or "the config"
Another approach is to notice that basically the problem with testing stems from the fact you hard-coded getting of the user.
Leaving aside the flawed approach you've taken to getting the place of configuration (you should be using something which implements the XDG spec), if you could easily override getting of the "root" directory (which is the user's home directory in your code), you could easily target your function to operate on the result of calling io/ioutil.TempDir.
So may be a way to go is to have an interface type like
type ConfigStore interface {
Dir() string
}
of which the Dir() method is supposed to return the path to the configuration store's root directory.
Your DeleteConfig() would then start to accept a single argument of type ConfigStore, and in your program you'd have a concrete implementation of it, and in your testing code — a stub implementing the same interface and managing a temporary directory.
Approach: go full-on virtualized
Right now, a work is being done on bringing filesystem virtualization right into the Go standard library, but while it's not there yet, 3rd-party packages which do that exist for ages, — for instance, github.com/spf13/afero.
Basically, they allow you to not use os directly but write all your code in a way so that instead of the os package it calls methods on an instance of a type implementing particular interface: in the production code that object is a thin shim for the os package, and in the testing code it's replaced by whatever you wish; afero has a readily-available in-memory FS backend to do this.
Writing a unit test for filesystem checking is not trivial. You should NOT create a real file on the system, because then your test will depend on the I/O of the file system itself. The last resort is mocking the filesystem. There are quite a few powerful libraries like spf13/afero for this purpose (mocking of a filesystem). These packages will create temporary files in the background and clean up afterward.
main.go
package main
import (
"log"
"os/user"
"path/filepath"
iowrap "github.com/spf13/afero"
)
var (
// FS is simulated filesystem interface
FS iowrap.Fs
// FSUtil is the struct of the simulated interface
FSUtil *iowrap.Afero
)
func init() {
FS = iowrap.NewOsFs()
FSUtil = &iowrap.Afero{Fs: FS}
}
// DeleteConfig removes ~/.config/godot if exists
func DeleteConfig() error {
usr, err := user.Current()
if err != nil {
return err
}
path := filepath.Join(usr.HomeDir, ".config", "godot")
log.Println(path)
err = FSUtil.RemoveAll(path)
return err
}
func main() {
err := DeleteConfig()
if err != nil {
log.Fatal(err)
}
}
main_test.go
package main
import (
"os/user"
"path/filepath"
"testing"
iowrap "github.com/spf13/afero"
)
func init() {
FS = iowrap.NewMemMapFs()
FSUtil = &iowrap.Afero{Fs: FS}
usr, _ := user.Current()
pathDir := filepath.Join(usr.HomeDir, ".config")
filePath := filepath.Join(pathDir, "godot")
FS.MkdirAll(pathDir, 0755)
iowrap.WriteFile(FS, filePath, []byte("0-7\n"), 0644)
}
const (
succeed = "\u2713"
failed = "\u2717"
)
func TestDeleteConfig(t *testing.T) {
t.Log("Given the need to test downloading a webpage content")
{
usr, _ := user.Current()
pathDir := filepath.Join(usr.HomeDir, ".config")
filePath := filepath.Join(pathDir, "godot")
t.Logf("\tTest 0:\tWhen deleting the %v with 0644 permissions", filePath)
{
err := DeleteConfig()
if err != nil {
t.Fatalf("\t%s\tThe file couldn't be deleted: %v", failed, err)
}
t.Logf("\t%s\tThe file has been successfully deleted.", succeed)
}
}
}
Functional test:
touch C:/Users/drpan/.config/godot
ls -l C:/Users/drpan/.config/godot Output: -rw-r--r-- 1 drpan 197609 0 Nov 2 19:38 C:/Users/drpan/.config/godot
./deletedirectory.exe
ls -l C:/Users/drpan/.config/godot Output: ls: cannot access 'C:/Users/drpan/.config/godot': No such file or directory
Unit Test:
$ touch C:/Users/drpan/.config/godot
$ go test
2020/11/02 19:55:35 C:\Users\drpan\.config\godot
PASS
ok github.com/drpaneas/deletedirectory 0.162s
$ ls -l C:/Users/drpan/.config/godot Output: -rw-r--r-- 1 drpan 197609 0 Nov 2 19:55 C:/Users/drpan/.config/godot
I am trying to write in newly generated file in a specific attached volume to my container (a directory)
however i am not sure about the correct the syntax. Below my code:
// Write the certificates to disk
f, _ := os.Create(filepath.Join("/data/certs/", "chamscertificate.pem"))
f.Write(cert)
f.Close()
f, _ = os.Create("key.pem")
f.Write(key)
f.Close()
}
when executing "go run .", i get the "key.pem" but not the "certificate.pem".
You don't check for errors. If the file wasn't created, the information about why the file wasn't created will be in the error return value from os.Create.
f, err := os.Create(filepath.Join("/data/certs/", "chamscertificate.pem"))
if err != nil {
log.Fatal(err)
}
... etc.
Note that f.Write and f.Close also return errors that should be checked.
fsync doc states
Calling fsync() does not necessarily ensure that the entry in the directory containing the file has also reached disk. For that an explicit fsync() on a file descriptor for the directory is
also needed.
I'm trying to sync a directory to the network mapped drive using SMB on windows, similar to what fsync does on Linux.
The following go code snippet which works fine if the directory is stored on a local drive but fails if it is on a network mapped folder.
func main() {
dir := "Z:\\smb-test" // Path to network mapped drive
f, err := openDir(dir)
if err != nil {
log.Fatal(err)
}
// Works fine if the path is located on a local disk but
// fails if the directory is on a network mapped drive
if err := f.Sync(); err != nil {
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
}
func openDir(path string) (*os.File, error) {
fd, err := openDirWin(path)
if err != nil {
return nil, err
}
return os.NewFile(uintptr(fd), path), nil
}
func openDirWin(path string) (fd syscall.Handle, err error) {
pathp, err := syscall.UTF16PtrFromString(path)
if err != nil {
return syscall.InvalidHandle, err
}
access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE)
sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE)
createmode := uint32(syscall.OPEN_EXISTING)
fl := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
return syscall.CreateFile(pathp, access, sharemode, nil, createmode, fl, 0)
}
The program fails with
Z:\\smb-test Incorrect function.
MSDN states that the handle passed in should either be a handle to a file, or to a volume, but says nothing about directories.
https://learn.microsoft.com/en-us/windows/desktop/api/FileAPI/nf-fileapi-flushfilebuffers
And FlushFileBuffers is not listed as a function that accepts directory handles
https://learn.microsoft.com/en-us/windows/desktop/fileio/obtaining-a-handle-to-a-directory
I also noticed the flushfilebuffers system call fails with Invalid Device Request for a network mapped drive
So the question is how do I sync a directory on windows?
Or is window's buffered I/O fundamentally different from POSIX and we don't need to flush a directory when a file inside it is modified?
The short answer to my question is there's no way to sync directories on windows using system calls.FlushFileBuffers works for files and volumes, NOT Directories.
Read https://social.msdn.microsoft.com/Forums/Windowsdesktop/en-US/847a735b-f21a-4be2-880b-12660e5b98b4/flushfilebuffers-system-call-fails-on-network-mapped-drive and
https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/0e0b734b-2b73-414d-8833-8d2eed7043f6/sync-directories-on-windows
for more details.
I'm trying to make simple read settings from config file. Both files - config.json and Settings.go, are in the same folder. But I'm always getting "The system cannot find the file specified." What I'm doing wrong?
func GetDbConnectionString() string {
file, err := os.Open("config.json")
if err != nil {
log.Fatal(err)
}
decoder := json.NewDecoder(file)
settings := Settings{}
err1 := decoder.Decode(&settings)
if err1 != nil {
fmt.Println("error:", err1)
}
log.Print(&settings)
return fmt.Sprintf("%s:%s#/%s", settings.login, settings.password, settings.database)
}
Your settings.json is not in the same directory as your main.go. If you invoke either go run main.go, or go build . && ./app, the current path will be .../app/ which does not contain the settings.json file.
Try copying your settings.json file to the same directory as your app, local invocation will work (it will still fail if you run from a separate directory though).