Skip to content

Commit

Permalink
time: fix zoneinfo.zip locating logic when built with -trimpath
Browse files Browse the repository at this point in the history
When the test binary is built with the -trimpath flag,
runtime.GOROOT() is invalid, and must not be used to locate
GOROOT/lib/time/zoneinfo.zip. (We can use other sources instead.)

However, the test for the package expects zoneinfo.zip to definitely
exist. 'go test' runs the test binary in the directory containing its
source code — in this case GOROOT/src/time — so we can use that
information to find the zoneinfo.zip file when runtime.GOROOT isn't
available.

For #51483

Change-Id: I9de35252a988d146b5d746794323214d400e64e5
Reviewed-on: https://go-review.googlesource.com/c/go/+/391814
Trust: Bryan Mills <bcmills@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
  • Loading branch information
Bryan C. Mills committed Mar 18, 2022
1 parent a682a5c commit 9465878
Show file tree
Hide file tree
Showing 17 changed files with 98 additions and 62 deletions.
12 changes: 8 additions & 4 deletions src/time/export_android_test.go
Expand Up @@ -4,9 +4,13 @@

package time

func ForceAndroidTzdataForTest(tzdata bool) {
forceZipFileForTesting(false)
if tzdata {
zoneSources = zoneSources[:len(zoneSources)-1]
func ForceAndroidTzdataForTest() (undo func()) {
allowGorootSource = false
origLoadFromEmbeddedTZData := loadFromEmbeddedTZData
loadFromEmbeddedTZData = nil

return func() {
allowGorootSource = true
loadFromEmbeddedTZData = origLoadFromEmbeddedTZData
}
}
3 changes: 2 additions & 1 deletion src/time/export_test.go
Expand Up @@ -28,7 +28,8 @@ func ResetZoneinfoForTesting() {
}

var (
ForceZipFileForTesting = forceZipFileForTesting
DisablePlatformSources = disablePlatformSources
GorootZoneSource = gorootZoneSource
ParseTimeZone = parseTimeZone
SetMono = (*Time).setMono
GetMono = (*Time).mono
Expand Down
4 changes: 2 additions & 2 deletions src/time/format_test.go
Expand Up @@ -408,8 +408,8 @@ func TestParseInLocation(t *testing.T) {
}

func TestLoadLocationZipFile(t *testing.T) {
ForceZipFileForTesting(true)
defer ForceZipFileForTesting(false)
undo := DisablePlatformSources()
defer undo()

_, err := LoadLocation("Australia/Sydney")
if err != nil {
Expand Down
21 changes: 13 additions & 8 deletions src/time/internal_test.go
Expand Up @@ -5,26 +5,31 @@
package time

func init() {
// force US/Pacific for time zone tests
// Force US/Pacific for time zone tests.
ForceUSPacificForTesting()
}

func initTestingZone() {
z, err := loadLocation("America/Los_Angeles", zoneSources[len(zoneSources)-1:])
// For hermeticity, use only tzinfo source from the test's GOROOT,
// not the system sources and not whatever GOROOT may happen to be
// set in the process's environment (if any).
// This test runs in GOROOT/src/time, so GOROOT is "../..",
// but it is theoretically possible
sources := []string{"../../lib/time/zoneinfo.zip"}
z, err := loadLocation("America/Los_Angeles", sources)
if err != nil {
panic("cannot load America/Los_Angeles for testing: " + err.Error() + "; you may want to use -tags=timetzdata")
}
z.name = "Local"
localLoc = *z
}

var OrigZoneSources = zoneSources
var origPlatformZoneSources []string = platformZoneSources

func forceZipFileForTesting(zipOnly bool) {
zoneSources = make([]string, len(OrigZoneSources))
copy(zoneSources, OrigZoneSources)
if zipOnly {
zoneSources = zoneSources[len(zoneSources)-1:]
func disablePlatformSources() (undo func()) {
platformZoneSources = nil
return func() {
platformZoneSources = origPlatformZoneSources
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/time/time_test.go
Expand Up @@ -1557,8 +1557,8 @@ func TestConcurrentTimerResetStop(t *testing.T) {
}

func TestTimeIsDST(t *testing.T) {
ForceZipFileForTesting(true)
defer ForceZipFileForTesting(false)
undo := DisablePlatformSources()
defer undo()

tzWithDST, err := LoadLocation("Australia/Sydney")
if err != nil {
Expand Down Expand Up @@ -1619,8 +1619,8 @@ func TestTimeAddSecOverflow(t *testing.T) {

// Issue 49284: time: ParseInLocation incorrectly because of Daylight Saving Time
func TestTimeWithZoneTransition(t *testing.T) {
ForceZipFileForTesting(true)
defer ForceZipFileForTesting(false)
undo := DisablePlatformSources()
defer undo()

loc, err := LoadLocation("Asia/Shanghai")
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions src/time/tzdata_test.go
Expand Up @@ -17,8 +17,8 @@ var zones = []string{
}

func TestEmbeddedTZData(t *testing.T) {
time.ForceZipFileForTesting(true)
defer time.ForceZipFileForTesting(false)
undo := time.DisablePlatformSources()
defer undo()

for _, zone := range zones {
ref, err := time.LoadLocation(zone)
Expand Down
2 changes: 1 addition & 1 deletion src/time/zoneinfo.go
Expand Up @@ -665,7 +665,7 @@ func LoadLocation(name string) (*Location, error) {
firstErr = err
}
}
if z, err := loadLocation(name, zoneSources); err == nil {
if z, err := loadLocation(name, platformZoneSources); err == nil {
return z, nil
} else if firstErr == nil {
firstErr = err
Expand Down
13 changes: 10 additions & 3 deletions src/time/zoneinfo_android.go
Expand Up @@ -10,14 +10,12 @@ package time

import (
"errors"
"runtime"
"syscall"
)

var zoneSources = []string{
var platformZoneSources = []string{
"/system/usr/share/zoneinfo/tzdata",
"/data/misc/zoneinfo/current/tzdata",
runtime.GOROOT() + "/lib/time/zoneinfo.zip",
}

func initLocal() {
Expand All @@ -29,6 +27,15 @@ func init() {
loadTzinfoFromTzdata = androidLoadTzinfoFromTzdata
}

var allowGorootSource = true

func gorootZoneSource(goroot string) (string, bool) {
if goroot == "" || !allowGorootSource {
return "", false
}
return goroot + "/lib/time/zoneinfo.zip", true
}

func androidLoadTzinfoFromTzdata(file, name string) ([]byte, error) {
const (
headersize = 12 + 3*4
Expand Down
4 changes: 2 additions & 2 deletions src/time/zoneinfo_android_test.go
Expand Up @@ -10,8 +10,8 @@ import (
)

func TestAndroidTzdata(t *testing.T) {
ForceAndroidTzdataForTest(true)
defer ForceAndroidTzdataForTest(false)
undo := ForceAndroidTzdataForTest()
defer undo()
if _, err := LoadLocation("America/Los_Angeles"); err != nil {
t.Error(err)
}
Expand Down
14 changes: 14 additions & 0 deletions src/time/zoneinfo_goroot.go
@@ -0,0 +1,14 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build !ios && !android

package time

func gorootZoneSource(goroot string) (string, bool) {
if goroot == "" {
return "", false
}
return goroot + "/lib/time/zoneinfo.zip", true
}
16 changes: 8 additions & 8 deletions src/time/zoneinfo_ios.go
Expand Up @@ -7,20 +7,20 @@
package time

import (
"runtime"
"syscall"
)

var zoneSources = []string{
getZoneRoot() + "/zoneinfo.zip",
}
var platformZoneSources []string // none on iOS

func getZoneRoot() string {
func gorootZoneSource(goroot string) (string, bool) {
// The working directory at initialization is the root of the
// app bundle: "/private/.../bundlename.app". That's where we
// keep zoneinfo.zip for tethered iOS builds.
// For self-hosted iOS builds, the zoneinfo.zip is in GOROOT.
roots := []string{runtime.GOROOT() + "/lib/time"}
var roots []string
if goroot != "" {
roots = append(roots, goroot+"/lib/time")
}
wd, err := syscall.Getwd()
if err == nil {
roots = append(roots, wd)
Expand All @@ -33,10 +33,10 @@ func getZoneRoot() string {
}
defer syscall.Close(fd)
if err := syscall.Fstat(fd, &st); err == nil {
return r
return r + "/zoneinfo.zip", true
}
}
return "/XXXNOEXIST"
return "", false
}

func initLocal() {
Expand Down
4 changes: 1 addition & 3 deletions src/time/zoneinfo_js.go
Expand Up @@ -7,15 +7,13 @@
package time

import (
"runtime"
"syscall/js"
)

var zoneSources = []string{
var platformZoneSources = []string{
"/usr/share/zoneinfo/",
"/usr/share/lib/zoneinfo/",
"/usr/lib/locale/TZ/",
runtime.GOROOT() + "/lib/time/zoneinfo.zip",
}

func initLocal() {
Expand Down
5 changes: 1 addition & 4 deletions src/time/zoneinfo_plan9.go
Expand Up @@ -7,13 +7,10 @@
package time

import (
"runtime"
"syscall"
)

var zoneSources = []string{
runtime.GOROOT() + "/lib/time/zoneinfo.zip",
}
var platformZoneSources []string // none on Plan 9

func isSpace(r rune) bool {
return r == ' ' || r == '\t' || r == '\n'
Expand Down
17 changes: 14 additions & 3 deletions src/time/zoneinfo_read.go
Expand Up @@ -528,7 +528,7 @@ func loadTzinfo(name string, source string) ([]byte, error) {
// and parsed is returned as a Location.
func loadLocation(name string, sources []string) (z *Location, firstErr error) {
for _, source := range sources {
var zoneData, err = loadTzinfo(name, source)
zoneData, err := loadTzinfo(name, source)
if err == nil {
if z, err = LoadLocationFromTZData(name, zoneData); err == nil {
return z, nil
Expand All @@ -539,9 +539,20 @@ func loadLocation(name string, sources []string) (z *Location, firstErr error) {
}
}
if loadFromEmbeddedTZData != nil {
zonedata, err := loadFromEmbeddedTZData(name)
zoneData, err := loadFromEmbeddedTZData(name)
if err == nil {
if z, err = LoadLocationFromTZData(name, []byte(zonedata)); err == nil {
if z, err = LoadLocationFromTZData(name, []byte(zoneData)); err == nil {
return z, nil
}
}
if firstErr == nil && err != syscall.ENOENT {
firstErr = err
}
}
if source, ok := gorootZoneSource(runtime.GOROOT()); ok {
zoneData, err := loadTzinfo(name, source)
if err == nil {
if z, err = LoadLocationFromTZData(name, zoneData); err == nil {
return z, nil
}
}
Expand Down
22 changes: 13 additions & 9 deletions src/time/zoneinfo_test.go
Expand Up @@ -66,8 +66,8 @@ func TestLoadLocationValidatesNames(t *testing.T) {
}

func TestVersion3(t *testing.T) {
time.ForceZipFileForTesting(true)
defer time.ForceZipFileForTesting(false)
undo := time.DisablePlatformSources()
defer undo()
_, err := time.LoadLocation("Asia/Jerusalem")
if err != nil {
t.Fatal(err)
Expand All @@ -78,8 +78,8 @@ func TestVersion3(t *testing.T) {
// transition time. To do this we explicitly check early dates in a
// couple of specific timezones.
func TestFirstZone(t *testing.T) {
time.ForceZipFileForTesting(true)
defer time.ForceZipFileForTesting(false)
undo := time.DisablePlatformSources()
defer undo()

const format = "Mon, 02 Jan 2006 15:04:05 -0700 (MST)"
var tests = []struct {
Expand Down Expand Up @@ -128,16 +128,20 @@ func TestLocationNames(t *testing.T) {
}

func TestLoadLocationFromTZData(t *testing.T) {
time.ForceZipFileForTesting(true)
defer time.ForceZipFileForTesting(false)
undo := time.DisablePlatformSources()
defer undo()

const locationName = "Asia/Jerusalem"
reference, err := time.LoadLocation(locationName)
if err != nil {
t.Fatal(err)
}

tzinfo, err := time.LoadTzinfo(locationName, time.OrigZoneSources[len(time.OrigZoneSources)-1])
gorootSource, ok := time.GorootZoneSource("../..")
if !ok {
t.Fatal("Failed to locate tzinfo source in GOROOT.")
}
tzinfo, err := time.LoadTzinfo(locationName, gorootSource)
if err != nil {
t.Fatal(err)
}
Expand All @@ -153,8 +157,8 @@ func TestLoadLocationFromTZData(t *testing.T) {

// Issue 30099.
func TestEarlyLocation(t *testing.T) {
time.ForceZipFileForTesting(true)
defer time.ForceZipFileForTesting(false)
undo := time.DisablePlatformSources()
defer undo()

const locName = "America/New_York"
loc, err := time.LoadLocation(locName)
Expand Down
6 changes: 2 additions & 4 deletions src/time/zoneinfo_unix.go
Expand Up @@ -12,17 +12,15 @@
package time

import (
"runtime"
"syscall"
)

// Many systems use /usr/share/zoneinfo, Solaris 2 has
// /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ.
var zoneSources = []string{
var platformZoneSources = []string{
"/usr/share/zoneinfo/",
"/usr/share/lib/zoneinfo/",
"/usr/lib/locale/TZ/",
runtime.GOROOT() + "/lib/time/zoneinfo.zip",
}

func initLocal() {
Expand Down Expand Up @@ -57,7 +55,7 @@ func initLocal() {
return
}
} else if tz != "" && tz != "UTC" {
if z, err := loadLocation(tz, zoneSources); err == nil {
if z, err := loadLocation(tz, platformZoneSources); err == nil {
localLoc = *z
return
}
Expand Down
5 changes: 1 addition & 4 deletions src/time/zoneinfo_windows.go
Expand Up @@ -7,13 +7,10 @@ package time
import (
"errors"
"internal/syscall/windows/registry"
"runtime"
"syscall"
)

var zoneSources = []string{
runtime.GOROOT() + "/lib/time/zoneinfo.zip",
}
var platformZoneSources []string // none: Windows uses system calls instead

// TODO(rsc): Fall back to copy of zoneinfo files.

Expand Down

0 comments on commit 9465878

Please sign in to comment.