diff --git a/py/file.go b/py/file.go index c60013ad..3d9f0185 100644 --- a/py/file.go +++ b/py/file.go @@ -166,71 +166,10 @@ func (o *File) M__exit__(exc_type, exc_value, traceback Object) (Object, error) } func OpenFile(filename, mode string, buffering int) (Object, error) { - var fileMode FileMode - var truncate bool - var exclusive bool - - for _, m := range mode { - switch m { - case 'r': - if fileMode&FileReadWrite != 0 { - return nil, ExceptionNewf(ValueError, "must have exactly one of create/read/write/append mode") - } - fileMode |= FileRead - - case 'w': - if fileMode&FileReadWrite != 0 { - return nil, ExceptionNewf(ValueError, "must have exactly one of create/read/write/append mode") - } - fileMode |= FileWrite - truncate = true - - case 'x': - if fileMode&FileReadWrite != 0 { - return nil, ExceptionNewf(ValueError, "must have exactly one of create/read/write/append mode") - } - fileMode |= FileWrite - exclusive = true - - case 'a': - if fileMode&FileReadWrite != 0 { - return nil, ExceptionNewf(ValueError, "must have exactly one of create/read/write/append mode") - } - fileMode |= FileWrite - truncate = false - - case '+': - if fileMode&FileReadWrite == 0 { - return nil, ExceptionNewf(ValueError, "Must have exactly one of create/read/write/append mode and at most one plus") - } - - truncate = (fileMode & FileWrite) != 0 - fileMode |= FileReadWrite - - case 'b': - if fileMode&FileReadWrite == 0 { - return nil, ExceptionNewf(ValueError, "Must have exactly one of create/read/write/append mode and at most one plus") - } - - if fileMode&FileText != 0 { - return nil, ExceptionNewf(ValueError, "can't have text and binary mode at once") - } - - fileMode |= FileBinary - - case 't': - if fileMode&FileReadWrite == 0 { - return nil, ExceptionNewf(ValueError, "Must have exactly one of create/read/write/append mode and at most one plus") - } - - if fileMode&FileBinary != 0 { - return nil, ExceptionNewf(ValueError, "can't have text and binary mode at once") - } - - fileMode |= FileText - } + fileMode, truncate, exclusive, err := FileModeFrom(mode) + if err != nil { + return nil, err } - var fmode int switch fileMode & FileReadWrite { @@ -280,3 +219,67 @@ func OpenFile(filename, mode string, buffering int) (Object, error) { // Check interface is satisfied var _ I__enter__ = (*File)(nil) var _ I__exit__ = (*File)(nil) + +func FileModeFrom(mode string) (perm FileMode, trunc, excl bool, err error) { + for _, m := range mode { + switch m { + case 'r': + if perm&FileReadWrite != 0 { + return 0, false, false, ExceptionNewf(ValueError, "must have exactly one of create/read/write/append mode") + } + perm |= FileRead + + case 'w': + if perm&FileReadWrite != 0 { + return 0, false, false, ExceptionNewf(ValueError, "must have exactly one of create/read/write/append mode") + } + perm |= FileWrite + trunc = true + + case 'x': + if perm&FileReadWrite != 0 { + return 0, false, false, ExceptionNewf(ValueError, "must have exactly one of create/read/write/append mode") + } + perm |= FileWrite + excl = true + + case 'a': + if perm&FileReadWrite != 0 { + return 0, false, false, ExceptionNewf(ValueError, "must have exactly one of create/read/write/append mode") + } + perm |= FileWrite + trunc = false + + case '+': + if perm&FileReadWrite == 0 { + return 0, false, false, ExceptionNewf(ValueError, "Must have exactly one of create/read/write/append mode and at most one plus") + } + + trunc = (perm & FileWrite) != 0 + perm |= FileReadWrite + + case 'b': + if perm&FileReadWrite == 0 { + return 0, false, false, ExceptionNewf(ValueError, "Must have exactly one of create/read/write/append mode and at most one plus") + } + + if perm&FileText != 0 { + return 0, false, false, ExceptionNewf(ValueError, "can't have text and binary mode at once") + } + + perm |= FileBinary + + case 't': + if perm&FileReadWrite == 0 { + return 0, false, false, ExceptionNewf(ValueError, "Must have exactly one of create/read/write/append mode and at most one plus") + } + + if perm&FileBinary != 0 { + return 0, false, false, ExceptionNewf(ValueError, "can't have text and binary mode at once") + } + + perm |= FileText + } + } + return perm, trunc, excl, nil +} diff --git a/stdlib/os/os.go b/stdlib/os/os.go index d863575f..e8b3ec7a 100644 --- a/stdlib/os/os.go +++ b/stdlib/os/os.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "runtime" + "strconv" "strings" "github.com/go-python/gpython/py" @@ -44,15 +45,21 @@ func init() { initGlobals() methods := []*py.Method{ + py.MustNewMethod("_exit", _exit, 0, "Immediate program termination."), + py.MustNewMethod("fdopen", fdopen, 0, fdopen_doc), py.MustNewMethod("getcwd", getCwd, 0, "Get the current working directory"), py.MustNewMethod("getcwdb", getCwdb, 0, "Get the current working directory in a byte slice"), py.MustNewMethod("chdir", chdir, 0, "Change the current working directory"), py.MustNewMethod("getenv", getenv, 0, "Return the value of the environment variable key if it exists, or default if it doesn’t. key, default and the result are str."), py.MustNewMethod("getpid", getpid, 0, "Return the current process id."), + py.MustNewMethod("makedirs", makedirs, 0, makedirs_doc), + py.MustNewMethod("mkdir", mkdir, 0, mkdir_doc), py.MustNewMethod("putenv", putenv, 0, "Set the environment variable named key to the string value."), + py.MustNewMethod("remove", remove, 0, remove_doc), + py.MustNewMethod("removedirs", removedirs, 0, removedirs_doc), + py.MustNewMethod("rmdir", rmdir, 0, rmdir_doc), + py.MustNewMethod("system", system, 0, "Run shell commands, prints stdout directly to default"), py.MustNewMethod("unsetenv", unsetenv, 0, "Unset (delete) the environment variable named key."), - py.MustNewMethod("_exit", _exit, 0, "Immediate program termination."), - py.MustNewMethod("system", system, 0, "Run shell commands, prints stdout directly to deault"), } globals := py.StringDict{ "error": py.OSError, @@ -91,6 +98,53 @@ func getEnvVariables() py.StringDict { return dict } +const fdopen_doc = `# Supply os.fdopen()` + +func fdopen(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { + var ( + pyfd py.Object + pymode py.Object = py.String("r") + pybuffering py.Object = py.Int(-1) + pyencoding py.Object = py.None + ) + err := py.ParseTupleAndKeywords( + args, kwargs, + "i|s#is#", []string{"fd", "mode", "buffering", "encoding"}, + &pyfd, &pymode, &pybuffering, &pyencoding, + ) + if err != nil { + return nil, err + } + + // FIXME(sbinet): handle buffering + // FIXME(sbinet): handle encoding + + var ( + fd = uintptr(pyfd.(py.Int)) + name = strconv.Itoa(int(fd)) + mode string + ) + + switch v := pymode.(type) { + case py.String: + mode = string(v) + case py.Bytes: + mode = string(v) + } + + perm, _, _, err := py.FileModeFrom(mode) + if err != nil { + return nil, err + } + + f := os.NewFile(fd, name) + if f == nil { + return nil, py.ExceptionNewf(py.OSError, "Bad file descriptor") + } + + return &py.File{f, perm}, nil +} + // getCwd returns the current working directory. func getCwd(self py.Object, args py.Tuple) (py.Object, error) { dir, err := os.Getwd() @@ -150,6 +204,105 @@ func getpid(self py.Object, args py.Tuple) (py.Object, error) { return py.Int(os.Getpid()), nil } +const makedirs_doc = `makedirs(name [, mode=0o777][, exist_ok=False]) + +Super-mkdir; create a leaf directory and all intermediate ones. Works like +mkdir, except that any intermediate path segment (not just the rightmost) +will be created if it does not exist. If the target directory already +exists, raise an OSError if exist_ok is False. Otherwise no exception is +raised. This is recursive.` + +func makedirs(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { + var ( + pypath py.Object + pymode py.Object = py.Int(0o777) + pyok py.Object = py.False + ) + err := py.ParseTupleAndKeywords( + args, kwargs, + "s#|ip:makedirs", []string{"path", "mode", "exist_ok"}, + &pypath, &pymode, &pyok, + ) + if err != nil { + return nil, err + } + + var ( + path = "" + mode = os.FileMode(pymode.(py.Int)) + ) + switch v := pypath.(type) { + case py.String: + path = string(v) + case py.Bytes: + path = string(v) + } + + if pyok.(py.Bool) == py.False { + // check if leaf exists. + _, err := os.Stat(path) + // FIXME(sbinet): handle other errors. + if err == nil { + return nil, py.ExceptionNewf(py.FileExistsError, "File exists: '%s'", path) + } + } + + err = os.MkdirAll(path, mode) + if err != nil { + return nil, err + } + + return py.None, nil +} + +const mkdir_doc = `Create a directory. + +If dir_fd is not None, it should be a file descriptor open to a directory, + and path should be relative; path will then be relative to that directory. +dir_fd may not be implemented on your platform. + If it is unavailable, using it will raise a NotImplementedError. + +The mode argument is ignored on Windows.` + +func mkdir(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { + var ( + pypath py.Object + pymode py.Object = py.Int(511) + pydirfd py.Object = py.None + ) + err := py.ParseTupleAndKeywords( + args, kwargs, + "s#|ii:mkdir", []string{"path", "mode", "dir_fd"}, + &pypath, &pymode, &pydirfd, + ) + if err != nil { + return nil, err + } + + var ( + path = "" + mode = os.FileMode(pymode.(py.Int)) + ) + switch v := pypath.(type) { + case py.String: + path = string(v) + case py.Bytes: + path = string(v) + } + + if pydirfd != py.None { + // FIXME(sbinet) + return nil, py.ExceptionNewf(py.NotImplementedError, "mkdir(dir_fd=XXX) not implemented") + } + + err = os.Mkdir(path, mode) + if err != nil { + return nil, err + } + + return py.None, nil +} + // putenv sets the value of an environment variable named by the key. func putenv(self py.Object, args py.Tuple) (py.Object, error) { if len(args) != 2 { @@ -199,6 +352,114 @@ func _exit(self py.Object, args py.Tuple) (py.Object, error) { // can never retu return nil, nil } +const remove_doc = `Remove a file (same as unlink()). + +If dir_fd is not None, it should be a file descriptor open to a directory, + and path should be relative; path will then be relative to that directory. +dir_fd may not be implemented on your platform. + If it is unavailable, using it will raise a NotImplementedError.` + +func remove(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { + var ( + pypath py.Object + pydir py.Object = py.None + ) + err := py.ParseTupleAndKeywords(args, kwargs, "s#|i:remove", []string{"path", "dir_fd"}, &pypath, &pydir) + if err != nil { + return nil, err + } + + if pydir != py.None { + // FIXME(sbinet) ? + return nil, py.ExceptionNewf(py.NotImplementedError, "remove(dir_fd=XXX) not implemented") + } + + var name string + switch v := pypath.(type) { + case py.String: + name = string(v) + case py.Bytes: + name = string(v) + } + + err = os.Remove(name) + if err != nil { + return nil, err + } + + return py.None, nil +} + +const removedirs_doc = `removedirs(name) + +Super-rmdir; remove a leaf directory and all empty intermediate +ones. Works like rmdir except that, if the leaf directory is +successfully removed, directories corresponding to rightmost path +segments will be pruned away until either the whole path is +consumed or an error occurs. Errors during this latter phase are +ignored -- they generally mean that a directory was not empty.` + +func removedirs(self py.Object, args py.Tuple) (py.Object, error) { + var pypath py.Object + err := py.ParseTuple(args, "s#:rmdir", &pypath) + if err != nil { + return nil, err + } + + var name string + switch v := pypath.(type) { + case py.String: + name = string(v) + case py.Bytes: + name = string(v) + } + + err = os.RemoveAll(name) + if err != nil { + return nil, err + } + + return py.None, nil +} + +const rmdir_doc = `Remove a directory. + +If dir_fd is not None, it should be a file descriptor open to a directory, + and path should be relative; path will then be relative to that directory. +dir_fd may not be implemented on your platform. + If it is unavailable, using it will raise a NotImplementedError.` + +func rmdir(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { + var ( + pypath py.Object + pydir py.Object = py.None + ) + err := py.ParseTupleAndKeywords(args, kwargs, "s#|i:rmdir", []string{"path", "dir_fd"}, &pypath, &pydir) + if err != nil { + return nil, err + } + + if pydir != py.None { + // FIXME(sbinet) ? + return nil, py.ExceptionNewf(py.NotImplementedError, "rmdir(dir_fd=XXX) not implemented") + } + + var name string + switch v := pypath.(type) { + case py.String: + name = string(v) + case py.Bytes: + name = string(v) + } + + err = os.Remove(name) + if err != nil { + return nil, err + } + + return py.None, nil +} + // os.system(command string) this function runs a shell command and directs the output to standard output. func system(self py.Object, args py.Tuple) (py.Object, error) { if len(args) != 1 { diff --git a/stdlib/os/testdata/test.py b/stdlib/os/testdata/test.py index 100f48df..bdbf606e 100644 --- a/stdlib/os/testdata/test.py +++ b/stdlib/os/testdata/test.py @@ -118,4 +118,62 @@ else: print("os."+k+": [OK]") +## fdopen +import tempfile +fd, tmp = tempfile.mkstemp() +f = os.fdopen(fd, "w+") +## if f.name != str(fd): +## print("invalid fd-name:", f.name) +f.close() +os.remove(tmp) + +## mkdir,rmdir,remove,removedirs +import tempfile +try: + top = tempfile.mkdtemp(prefix="gpython-os-test-") + dir1 = top + os.sep + "dir1" + dir2 = top + os.sep + "dir2" + dir11 = top + os.sep + "dir1" + os.sep + "dir11" + fname = dir2 + os.sep + "foo.txt" + os.mkdir(dir1) + os.rmdir(dir1) + os.mkdir(dir1) + os.mkdir(dir2) + os.mkdir(dir11) + os.removedirs(dir1) + try: + os.mkdir(dir11) + print("creating nested dirs with os.mkdir should have failed") + except SystemError as e: + print("caught: SystemError - no such file or directory [OK]") + except Exception as e: + print("caught: %s" % e) + + os.makedirs(dir11) + try: + os.makedirs(dir11) + print("creating already existing dirs should have failed") + except FileExistsError as e: + print("caught: FileExistsError [OK]") + except Exception as e: + print("INVALID error caught: %s" % e) + os.makedirs(dir11, exist_ok=True) + + with open(fname, "w+") as f: + pass + try: + os.rmdir(dir2) + print("removing a non-empty directory should have failed") + except SystemError as e: + print("caught: SystemError - directory not empty [OK]") + except Exception as e: + print("INVALID error caught: %s" % e) + os.remove(fname) + os.rmdir(dir2) +except Exception as e: + print("could not create/remove directories: %s" % e) +finally: + os.removedirs(top) + print("os.{mkdir,rmdir,remove,removedirs} worked as expected") + print("OK") diff --git a/stdlib/os/testdata/test_golden.txt b/stdlib/os/testdata/test_golden.txt index 8aae577d..43c83011 100644 --- a/stdlib/os/testdata/test_golden.txt +++ b/stdlib/os/testdata/test_golden.txt @@ -24,4 +24,8 @@ os.pathsep: [OK] os.linesep: [OK] os.devnull: [OK] os.altsep: [OK] +caught: SystemError - no such file or directory [OK] +caught: FileExistsError [OK] +caught: SystemError - directory not empty [OK] +os.{mkdir,rmdir,remove,removedirs} worked as expected OK diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index c78c2209..7061f486 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -25,6 +25,7 @@ import ( _ "github.com/go-python/gpython/stdlib/os" _ "github.com/go-python/gpython/stdlib/string" _ "github.com/go-python/gpython/stdlib/sys" + _ "github.com/go-python/gpython/stdlib/tempfile" _ "github.com/go-python/gpython/stdlib/time" ) diff --git a/stdlib/tempfile/tempfile.go b/stdlib/tempfile/tempfile.go new file mode 100644 index 00000000..c31612da --- /dev/null +++ b/stdlib/tempfile/tempfile.go @@ -0,0 +1,283 @@ +// Copyright 2022 The go-python Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package tempfile provides the implementation of the python's 'tempfile' module. +package tempfile + +import ( + "fmt" + "os" + + "github.com/go-python/gpython/py" +) + +var ( + gblTempDir py.Object = py.None +) + +const tempfile_doc = `Temporary files. + +This module provides generic, low- and high-level interfaces for +creating temporary files and directories. All of the interfaces +provided by this module can be used without fear of race conditions +except for 'mktemp'. 'mktemp' is subject to race conditions and +should not be used; it is provided for backward compatibility only. + +The default path names are returned as str. If you supply bytes as +input, all return values will be in bytes. Ex: + + >>> tempfile.mkstemp() + (4, '/tmp/tmptpu9nin8') + >>> tempfile.mkdtemp(suffix=b'') + b'/tmp/tmppbi8f0hy' + +This module also provides some data items to the user: + + TMP_MAX - maximum number of names that will be tried before + giving up. + tempdir - If this is set to a string before the first use of + any routine from this module, it will be considered as + another candidate location to store temporary files.` + +func init() { + py.RegisterModule(&py.ModuleImpl{ + Info: py.ModuleInfo{ + Name: "tempfile", + Doc: tempfile_doc, + }, + Methods: []*py.Method{ + py.MustNewMethod("gettempdir", gettempdir, 0, gettempdir_doc), + py.MustNewMethod("gettempdirb", gettempdirb, 0, gettempdirb_doc), + py.MustNewMethod("mkdtemp", mkdtemp, 0, mkdtemp_doc), + py.MustNewMethod("mkstemp", mkstemp, 0, mkstemp_doc), + }, + Globals: py.StringDict{ + "tempdir": gblTempDir, + }, + }) +} + +const gettempdir_doc = `Returns tempfile.tempdir as str.` + +func gettempdir(self py.Object) (py.Object, error) { + // FIXME(sbinet): lock access to glbTempDir? + if gblTempDir != py.None { + switch dir := gblTempDir.(type) { + case py.String: + return dir, nil + case py.Bytes: + return py.String(dir), nil + default: + return nil, py.ExceptionNewf(py.TypeError, "expected str, bytes or os.PathLike object, not %s", dir.Type().Name) + } + } + return py.String(os.TempDir()), nil +} + +const gettempdirb_doc = `Returns tempfile.tempdir as bytes.` + +func gettempdirb(self py.Object) (py.Object, error) { + // FIXME(sbinet): lock access to glbTempDir? + if gblTempDir != py.None { + switch dir := gblTempDir.(type) { + case py.String: + return py.Bytes(dir), nil + case py.Bytes: + return dir, nil + default: + return nil, py.ExceptionNewf(py.TypeError, "expected str, bytes or os.PathLike object, not %s", dir.Type().Name) + } + } + return py.Bytes(os.TempDir()), nil +} + +const mkdtemp_doc = `mkdtemp(suffix=None, prefix=None, dir=None) + User-callable function to create and return a unique temporary + directory. The return value is the pathname of the directory. + + Arguments are as for mkstemp, except that the 'text' argument is + not accepted. + + The directory is readable, writable, and searchable only by the + creating user. + + Caller is responsible for deleting the directory when done with it.` + +func mkdtemp(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { + var ( + pysuffix py.Object = py.None + pyprefix py.Object = py.None + pydir py.Object = py.None + ) + err := py.ParseTupleAndKeywords(args, kwargs, + "|z#z#z#:mkdtemp", + []string{"suffix", "prefix", "dir"}, + &pysuffix, &pyprefix, &pydir, + ) + if err != nil { + return nil, err + } + + str := func(v py.Object, typ *uint8) string { + switch v := v.(type) { + case py.Bytes: + *typ = 2 + return string(v) + case py.String: + *typ = 1 + return string(v) + case py.NoneType: + *typ = 0 + return "" + default: + panic(fmt.Errorf("tempfile: invalid type %T (v=%+v)", v, v)) + } + } + + var ( + t1, t2, t3 uint8 + + suffix = str(pysuffix, &t1) + prefix = str(pyprefix, &t2) + dir = str(pydir, &t3) + pattern = prefix + "*" + suffix + ) + + cmp := func(t1, t2 uint8) bool { + if t1 > 0 && t2 > 0 { + return t1 == t2 + } + return true + } + + if !cmp(t1, t2) || !cmp(t1, t3) || !cmp(t2, t3) { + return nil, py.ExceptionNewf(py.TypeError, "Can't mix bytes and non-bytes in path components") + } + + tmp, err := os.MkdirTemp(dir, pattern) + if err != nil { + return nil, err + } + + typ := t1 + if typ == 0 { + typ = t2 + } + if typ == 0 { + typ = t3 + } + + switch typ { + case 2: + return py.Bytes(tmp), nil + default: + return py.String(tmp), nil + } +} + +const mkstemp_doc = `mkstemp(suffix=None, prefix=None, dir=None, text=False) + +User-callable function to create and return a unique temporary +file. The return value is a pair (fd, name) where fd is the +file descriptor returned by os.open, and name is the filename. + +If 'suffix' is not None, the file name will end with that suffix, +otherwise there will be no suffix. + +If 'prefix' is not None, the file name will begin with that prefix, +otherwise a default prefix is used. + +If 'dir' is not None, the file will be created in that directory, +otherwise a default directory is used. + +If 'text' is specified and true, the file is opened in text +mode. Else (the default) the file is opened in binary mode. + +If any of 'suffix', 'prefix' and 'dir' are not None, they must be the +same type. If they are bytes, the returned name will be bytes; str +otherwise. + +The file is readable and writable only by the creating user ID. +If the operating system uses permission bits to indicate whether a +file is executable, the file is executable by no one. The file +descriptor is not inherited by children of this process. + +Caller is responsible for deleting the file when done with it.` + +func mkstemp(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) { + var ( + pysuffix py.Object = py.None + pyprefix py.Object = py.None + pydir py.Object = py.None + pytext py.Object = py.False // FIXME(sbinet): can we do something with that? + ) + + err := py.ParseTupleAndKeywords(args, kwargs, + "|z#z#z#p:mkstemp", + []string{"suffix", "prefix", "dir", "text"}, + &pysuffix, &pyprefix, &pydir, &pytext, + ) + if err != nil { + return nil, err + } + + str := func(v py.Object, typ *uint8) string { + switch v := v.(type) { + case py.Bytes: + *typ = 2 + return string(v) + case py.String: + *typ = 1 + return string(v) + case py.NoneType: + *typ = 0 + return "" + default: + panic(fmt.Errorf("tempfile: invalid type %T (v=%+v)", v, v)) + } + } + + var ( + t1, t2, t3 uint8 + + suffix = str(pysuffix, &t1) + prefix = str(pyprefix, &t2) + dir = str(pydir, &t3) + pattern = prefix + "*" + suffix + ) + + cmp := func(t1, t2 uint8) bool { + if t1 > 0 && t2 > 0 { + return t1 == t2 + } + return true + } + + if !cmp(t1, t2) || !cmp(t1, t3) || !cmp(t2, t3) { + return nil, py.ExceptionNewf(py.TypeError, "Can't mix bytes and non-bytes in path components") + } + + f, err := os.CreateTemp(dir, pattern) + if err != nil { + return nil, err + } + + typ := t1 + if typ == 0 { + typ = t2 + } + if typ == 0 { + typ = t3 + } + + tuple := py.Tuple{py.Int(f.Fd())} + switch typ { + case 2: + tuple = append(tuple, py.Bytes(f.Name())) + default: + tuple = append(tuple, py.String(f.Name())) + } + + return tuple, nil +} diff --git a/stdlib/tempfile/tempfile_test.go b/stdlib/tempfile/tempfile_test.go new file mode 100644 index 00000000..33a75e98 --- /dev/null +++ b/stdlib/tempfile/tempfile_test.go @@ -0,0 +1,15 @@ +// Copyright 2022 The go-python Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tempfile_test + +import ( + "testing" + + "github.com/go-python/gpython/pytest" +) + +func TestTempfile(t *testing.T) { + pytest.RunScript(t, "./testdata/test.py") +} diff --git a/stdlib/tempfile/testdata/test.py b/stdlib/tempfile/testdata/test.py new file mode 100644 index 00000000..19cef5bc --- /dev/null +++ b/stdlib/tempfile/testdata/test.py @@ -0,0 +1,110 @@ +# Copyright 2022 The go-python Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +import tempfile +import os + +print("test tempfile") + +if not tempfile.tempdir is None: + print("tempfile.tempdir is not None: %s" % (tempfile.tempdir,)) +else: + print("tempfile.tempdir is None [OK]") + +v = tempfile.gettempdir() +if type(v) != type(""): + print("tempfile.gettempdir() returned %s (type=%s)" % (v, type(v))) + +v = tempfile.gettempdirb() +if type(v) != type(b""): + print("tempfile.gettempdirb() returned %s (type=%s)" % (v, type(v))) + +## mkdtemp +try: + tmp = tempfile.mkdtemp() + os.rmdir(tmp) + print("mkdtemp() [OK]") +except Exception as e: + print("could not create tmp dir w/ mkdtemp(): %s" % e) + +try: + tmp = tempfile.mkdtemp(prefix="prefix-", suffix="-suffix") + os.rmdir(tmp) + print("mkdtemp(prefix='prefix-', suffix='-suffix') [OK]") +except Exception as e: + print("could not create tmp dir w/ mkdtemp(prefix='prefix-', suffix='-suffix'): %s" % e) + +try: + top = tempfile.mkdtemp(prefix="prefix-", suffix="-suffix") + tmp = tempfile.mkdtemp(prefix="prefix-", suffix="-suffix", dir=top) + os.rmdir(tmp) + os.rmdir(top) + print("mkdtemp(prefix='prefix-', suffix='-suffix', dir=top) [OK]") +except Exception as e: + print("could not create tmp dir w/ mkdtemp(prefix='prefix-', suffix='-suffix', dir=top): %s" % e) + +try: + top = tempfile.mkdtemp(prefix="prefix-", suffix="-suffix") + tmp = tempfile.mkdtemp(prefix="prefix-", suffix="-suffix", dir=top) + os.removedirs(top) + print("mkdtemp(prefix='prefix-', suffix='-suffix', dir=top) [OK]") +except Exception as e: + print("could not create tmp dir w/ mkdtemp(prefix='prefix-', suffix='-suffix', dir=top): %s" % e) + +try: + tmp = tempfile.mkdtemp(prefix=b"prefix-", suffix="-suffix") + print("missing exception!") + os.rmdir(tmp) +except TypeError as e: + print("caught: %s [OK]" % e) +except Exception as e: + print("INVALID error caught: %s" % e) + +def remove(fd, name): + os.fdopen(fd).close() + os.remove(name) + +## mkstemp +try: + fd, tmp = tempfile.mkstemp() + remove(fd, tmp) + print("mkstemp() [OK]") +except Exception as e: + print("could not create tmp dir w/ mkstemp(): %s" % e) + +try: + fd, tmp = tempfile.mkstemp(prefix="prefix-", suffix="-suffix") + remove(fd, tmp) + print("mkstemp(prefix='prefix-', suffix='-suffix') [OK]") +except Exception as e: + print("could not create tmp dir w/ mkstemp(prefix='prefix-', suffix='-suffix'): %s" % e) + +try: + top = tempfile.mkdtemp(prefix="prefix-", suffix="-suffix") + fd, tmp = tempfile.mkstemp(prefix="prefix-", suffix="-suffix", dir=top) + remove(fd, tmp) + os.remove(top) + print("mkstemp(prefix='prefix-', suffix='-suffix', dir=top) [OK]") +except Exception as e: + print("could not create tmp dir w/ mkstemp(prefix='prefix-', suffix='-suffix', dir=top): %s" % e) + +try: + top = tempfile.mkdtemp(prefix="prefix-", suffix="-suffix") + fd, tmp = tempfile.mkstemp(prefix="prefix-", suffix="-suffix", dir=top) + os.fdopen(fd).close() ## needed on Windows. + os.removedirs(top) + print("mkstemp(prefix='prefix-', suffix='-suffix', dir=top) [OK]") +except Exception as e: + print("could not create tmp dir w/ mkstemp(prefix='prefix-', suffix='-suffix', dir=top): %s" % e) + +try: + fd, tmp = tempfile.mkstemp(prefix=b"prefix-", suffix="-suffix") + print("missing exception!") + remove(fd, tmp) +except TypeError as e: + print("caught: %s [OK]" % e) +except Exception as e: + print("INVALID error caught: %s" % e) + +print("OK") diff --git a/stdlib/tempfile/testdata/test_golden.txt b/stdlib/tempfile/testdata/test_golden.txt new file mode 100644 index 00000000..ff7814de --- /dev/null +++ b/stdlib/tempfile/testdata/test_golden.txt @@ -0,0 +1,13 @@ +test tempfile +tempfile.tempdir is None [OK] +mkdtemp() [OK] +mkdtemp(prefix='prefix-', suffix='-suffix') [OK] +mkdtemp(prefix='prefix-', suffix='-suffix', dir=top) [OK] +mkdtemp(prefix='prefix-', suffix='-suffix', dir=top) [OK] +caught: TypeError: "Can't mix bytes and non-bytes in path components" [OK] +mkstemp() [OK] +mkstemp(prefix='prefix-', suffix='-suffix') [OK] +mkstemp(prefix='prefix-', suffix='-suffix', dir=top) [OK] +mkstemp(prefix='prefix-', suffix='-suffix', dir=top) [OK] +caught: TypeError: "Can't mix bytes and non-bytes in path components" [OK] +OK