Skip to content

Commit c1bd5f3

Browse files
authored
Add a BinariesPath configuration option, for prefetching Postgres binaries before the test (#38)
* Refactor usage of paths throught embedded_postgres.go * Separate binaryExtractLocation from runtimePath in prepare_database.go * Add BinariesPath to the configuration; reuse unarchived binaries. * Added readme information about BinariesPath. * Fixed CI Lint comments. * Move Mkdir to after the unarchive, to make the Alpine tests happy. * Fixed typo * Fixed tests that failed on Alpine.
1 parent 05b3cc2 commit c1bd5f3

6 files changed

+174
-60
lines changed

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ This library aims to require as little configuration as possible, favouring over
4444
| Version | 12.1.0 |
4545
| RuntimePath | $USER_HOME/.embedded-postgres-go/extracted |
4646
| DataPath | $USER_HOME/.embedded-postgres-go/extracted/data |
47+
| BinariesPath | $USER_HOME/.embedded-postgres-go/extracted |
4748
| Port | 5432 |
4849
| StartTimeout | 15 Seconds |
4950

@@ -54,6 +55,12 @@ If a persistent data location is required, set *DataPath* to a directory outside
5455
If the *RuntimePath* directory is empty or already initialized but with an incompatible postgres version, it will be
5556
removed and Postgres reinitialized.
5657

58+
Postgres binaries will be downloaded and placed in *BinaryPath* if `BinaryPath/bin` doesn't exist.
59+
If the directory does exist, whatever binary version is placed there will be used (no version check
60+
is done).
61+
If your test need to run multiple different versions of Postgres for different tests, make sure
62+
*BinaryPath* is a subdirectory of *RuntimePath*.
63+
5764
A single Postgres instance can be created, started and stopped as follows
5865

5966
```go

config.go

+8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type Config struct {
1515
password string
1616
runtimePath string
1717
dataPath string
18+
binariesPath string
1819
locale string
1920
startTimeout time.Duration
2021
logger io.Writer
@@ -84,6 +85,13 @@ func (c Config) DataPath(path string) Config {
8485
return c
8586
}
8687

88+
// BinariesPath sets the path of the pre-downloaded postgres binaries.
89+
// If this option is left unset, the binaries will be downloaded.
90+
func (c Config) BinariesPath(path string) Config {
91+
c.binariesPath = path
92+
return c
93+
}
94+
8795
// Locale sets the default locale for initdb
8896
func (c Config) Locale(locale string) Config {
8997
c.locale = locale

embedded_postgres.go

+43-44
Original file line numberDiff line numberDiff line change
@@ -68,45 +68,62 @@ func (ep *EmbeddedPostgres) Start() error {
6868
return err
6969
}
7070

71-
cacheLocation, exists := ep.cacheLocator()
72-
if !exists {
73-
if err := ep.remoteFetchStrategy(); err != nil {
74-
return err
75-
}
71+
cacheLocation, cacheExists := ep.cacheLocator()
72+
73+
if ep.config.runtimePath == "" {
74+
ep.config.runtimePath = filepath.Join(filepath.Dir(cacheLocation), "extracted")
75+
}
76+
77+
if ep.config.dataPath == "" {
78+
ep.config.dataPath = filepath.Join(ep.config.runtimePath, "data")
7679
}
7780

78-
binaryExtractLocation := userRuntimePathOrDefault(ep.config.runtimePath, cacheLocation)
79-
if err := os.RemoveAll(binaryExtractLocation); err != nil {
80-
return fmt.Errorf("unable to clean up runtime directory %s with error: %s", binaryExtractLocation, err)
81+
if err := os.RemoveAll(ep.config.runtimePath); err != nil {
82+
return fmt.Errorf("unable to clean up runtime directory %s with error: %s", ep.config.runtimePath, err)
8183
}
8284

83-
if err := archiver.NewTarXz().Unarchive(cacheLocation, binaryExtractLocation); err != nil {
84-
return fmt.Errorf("unable to extract postgres archive %s to %s", cacheLocation, binaryExtractLocation)
85+
if ep.config.binariesPath == "" {
86+
ep.config.binariesPath = ep.config.runtimePath
87+
}
88+
89+
_, binDirErr := os.Stat(filepath.Join(ep.config.binariesPath, "bin"))
90+
if os.IsNotExist(binDirErr) {
91+
if !cacheExists {
92+
if err := ep.remoteFetchStrategy(); err != nil {
93+
return err
94+
}
95+
}
96+
97+
if err := archiver.NewTarXz().Unarchive(cacheLocation, ep.config.binariesPath); err != nil {
98+
return fmt.Errorf("unable to extract postgres archive %s to %s", cacheLocation, ep.config.binariesPath)
99+
}
85100
}
86101

87-
dataLocation := userDataPathOrDefault(ep.config.dataPath, binaryExtractLocation)
102+
if err := os.MkdirAll(ep.config.runtimePath, 0755); err != nil {
103+
return fmt.Errorf("unable to create runtime directory %s with error: %s", ep.config.runtimePath, err)
104+
}
88105

89-
reuseData := ep.config.dataPath != "" && dataDirIsValid(dataLocation, ep.config.version)
106+
reuseData := dataDirIsValid(ep.config.dataPath, ep.config.version)
90107

91108
if !reuseData {
92-
if err := os.RemoveAll(dataLocation); err != nil {
93-
return fmt.Errorf("unable to clean up data directory %s with error: %s", dataLocation, err)
109+
if err := os.RemoveAll(ep.config.dataPath); err != nil {
110+
return fmt.Errorf("unable to clean up data directory %s with error: %s", ep.config.dataPath, err)
94111
}
95112

96-
if err := ep.initDatabase(binaryExtractLocation, dataLocation, ep.config.username, ep.config.password, ep.config.locale, ep.config.logger); err != nil {
113+
if err := ep.initDatabase(ep.config.binariesPath, ep.config.runtimePath, ep.config.dataPath, ep.config.username, ep.config.password, ep.config.locale, ep.config.logger); err != nil {
97114
return err
98115
}
99116
}
100117

101-
if err := startPostgres(binaryExtractLocation, ep.config); err != nil {
118+
if err := startPostgres(ep.config); err != nil {
102119
return err
103120
}
104121

105122
ep.started = true
106123

107124
if !reuseData {
108125
if err := ep.createDatabase(ep.config.port, ep.config.username, ep.config.password, ep.config.database); err != nil {
109-
if stopErr := stopPostgres(binaryExtractLocation, ep.config); stopErr != nil {
126+
if stopErr := stopPostgres(ep.config); stopErr != nil {
110127
return fmt.Errorf("unable to stop database casused by error %s", err)
111128
}
112129

@@ -115,7 +132,7 @@ func (ep *EmbeddedPostgres) Start() error {
115132
}
116133

117134
if err := healthCheckDatabaseOrTimeout(ep.config); err != nil {
118-
if stopErr := stopPostgres(binaryExtractLocation, ep.config); stopErr != nil {
135+
if stopErr := stopPostgres(ep.config); stopErr != nil {
119136
return fmt.Errorf("unable to stop database casused by error %s", err)
120137
}
121138

@@ -127,13 +144,11 @@ func (ep *EmbeddedPostgres) Start() error {
127144

128145
// Stop will try to stop the Postgres process gracefully returning an error when there were any problems.
129146
func (ep *EmbeddedPostgres) Stop() error {
130-
cacheLocation, exists := ep.cacheLocator()
131-
if !exists || !ep.started {
147+
if !ep.started {
132148
return errors.New("server has not been started")
133149
}
134150

135-
binaryExtractLocation := userRuntimePathOrDefault(ep.config.runtimePath, cacheLocation)
136-
if err := stopPostgres(binaryExtractLocation, ep.config); err != nil {
151+
if err := stopPostgres(ep.config); err != nil {
137152
return err
138153
}
139154

@@ -142,10 +157,10 @@ func (ep *EmbeddedPostgres) Stop() error {
142157
return nil
143158
}
144159

145-
func startPostgres(binaryExtractLocation string, config Config) error {
146-
postgresBinary := filepath.Join(binaryExtractLocation, "bin/pg_ctl")
160+
func startPostgres(config Config) error {
161+
postgresBinary := filepath.Join(config.binariesPath, "bin/pg_ctl")
147162
postgresProcess := exec.Command(postgresBinary, "start", "-w",
148-
"-D", userDataPathOrDefault(config.dataPath, binaryExtractLocation),
163+
"-D", config.dataPath,
149164
"-o", fmt.Sprintf(`"-p %d"`, config.port))
150165
postgresProcess.Stderr = config.logger
151166
postgresProcess.Stdout = config.logger
@@ -157,10 +172,10 @@ func startPostgres(binaryExtractLocation string, config Config) error {
157172
return nil
158173
}
159174

160-
func stopPostgres(binaryExtractLocation string, config Config) error {
161-
postgresBinary := filepath.Join(binaryExtractLocation, "bin/pg_ctl")
175+
func stopPostgres(config Config) error {
176+
postgresBinary := filepath.Join(config.binariesPath, "bin/pg_ctl")
162177
postgresProcess := exec.Command(postgresBinary, "stop", "-w",
163-
"-D", userDataPathOrDefault(config.dataPath, binaryExtractLocation))
178+
"-D", config.dataPath)
164179
postgresProcess.Stderr = config.logger
165180
postgresProcess.Stdout = config.logger
166181

@@ -180,22 +195,6 @@ func ensurePortAvailable(port uint32) error {
180195
return nil
181196
}
182197

183-
func userRuntimePathOrDefault(userLocation, cacheLocation string) string {
184-
if userLocation != "" {
185-
return userLocation
186-
}
187-
188-
return filepath.Join(filepath.Dir(cacheLocation), "extracted")
189-
}
190-
191-
func userDataPathOrDefault(userLocation, runtimeLocation string) string {
192-
if userLocation != "" {
193-
return userLocation
194-
}
195-
196-
return filepath.Join(runtimeLocation, "data")
197-
}
198-
199198
func dataDirIsValid(dataDir string, version PostgresVersion) bool {
200199
pgVersion := filepath.Join(dataDir, "PG_VERSION")
201200

embedded_postgres_test.go

+93-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"testing"
1313
"time"
1414

15+
"github.com/mholt/archiver/v3"
1516
"github.com/stretchr/testify/assert"
1617
)
1718

@@ -118,7 +119,7 @@ func Test_ErrorWhenUnableToInitDatabase(t *testing.T) {
118119
return jarFile, true
119120
}
120121

121-
database.initDatabase = func(binaryExtractLocation, dataLocation, username, password, locale string, logger io.Writer) error {
122+
database.initDatabase = func(binaryExtractLocation, runtimePath, dataLocation, username, password, locale string, logger io.Writer) error {
122123
return errors.New("ah it did not work")
123124
}
124125

@@ -221,7 +222,7 @@ func Test_ErrorWhenCannotStartPostgresProcess(t *testing.T) {
221222
return jarFile, true
222223
}
223224

224-
database.initDatabase = func(binaryExtractLocation, dataLocation, username, password, locale string, logger io.Writer) error {
225+
database.initDatabase = func(binaryExtractLocation, runtimePath, dataLocation, username, password, locale string, logger io.Writer) error {
225226
return nil
226227
}
227228

@@ -424,3 +425,93 @@ func Test_ReuseData(t *testing.T) {
424425
shutdownDBAndFail(t, err, database)
425426
}
426427
}
428+
429+
func Test_CustomBinariesLocation(t *testing.T) {
430+
tempDir, err := ioutil.TempDir("", "prepare_database_test")
431+
if err != nil {
432+
panic(err)
433+
}
434+
435+
defer func() {
436+
if err := os.RemoveAll(tempDir); err != nil {
437+
panic(err)
438+
}
439+
}()
440+
441+
database := NewDatabase(DefaultConfig().
442+
BinariesPath(tempDir))
443+
444+
if err := database.Start(); err != nil {
445+
shutdownDBAndFail(t, err, database)
446+
}
447+
448+
if err := database.Stop(); err != nil {
449+
shutdownDBAndFail(t, err, database)
450+
}
451+
452+
// Delete cache to make sure unarchive doesn't happen again.
453+
cacheLocation, _ := database.cacheLocator()
454+
if err := os.RemoveAll(cacheLocation); err != nil {
455+
panic(err)
456+
}
457+
458+
if err := database.Start(); err != nil {
459+
shutdownDBAndFail(t, err, database)
460+
}
461+
462+
if err := database.Stop(); err != nil {
463+
shutdownDBAndFail(t, err, database)
464+
}
465+
}
466+
467+
func Test_PrefetchedBinaries(t *testing.T) {
468+
binTempDir, err := ioutil.TempDir("", "prepare_database_test_bin")
469+
if err != nil {
470+
panic(err)
471+
}
472+
473+
runtimeTempDir, err := ioutil.TempDir("", "prepare_database_test_runtime")
474+
if err != nil {
475+
panic(err)
476+
}
477+
478+
defer func() {
479+
if err := os.RemoveAll(binTempDir); err != nil {
480+
panic(err)
481+
}
482+
483+
if err := os.RemoveAll(runtimeTempDir); err != nil {
484+
panic(err)
485+
}
486+
}()
487+
488+
database := NewDatabase(DefaultConfig().
489+
BinariesPath(binTempDir).
490+
RuntimePath(runtimeTempDir))
491+
492+
// Download and unarchive postgres into the bindir.
493+
if err := database.remoteFetchStrategy(); err != nil {
494+
panic(err)
495+
}
496+
497+
cacheLocation, _ := database.cacheLocator()
498+
if err := archiver.NewTarXz().Unarchive(cacheLocation, binTempDir); err != nil {
499+
panic(err)
500+
}
501+
502+
// Expect everything to work without cacheLocator and/or remoteFetch abilities.
503+
database.cacheLocator = func() (string, bool) {
504+
return "", false
505+
}
506+
database.remoteFetchStrategy = func() error {
507+
return errors.New("did not work")
508+
}
509+
510+
if err := database.Start(); err != nil {
511+
shutdownDBAndFail(t, err, database)
512+
}
513+
514+
if err := database.Stop(); err != nil {
515+
shutdownDBAndFail(t, err, database)
516+
}
517+
}

prepare_database.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ import (
1414
"github.com/lib/pq"
1515
)
1616

17-
type initDatabase func(binaryExtractLocation, pgDataDir, username, password, locale string, logger io.Writer) error
17+
type initDatabase func(binaryExtractLocation, runtimePath, pgDataDir, username, password, locale string, logger io.Writer) error
1818
type createDatabase func(port uint32, username, password, database string) error
1919

20-
func defaultInitDatabase(binaryExtractLocation, pgDataDir, username, password, locale string, logger io.Writer) error {
21-
passwordFile, err := createPasswordFile(binaryExtractLocation, password)
20+
func defaultInitDatabase(binaryExtractLocation, runtimePath, pgDataDir, username, password, locale string, logger io.Writer) error {
21+
passwordFile, err := createPasswordFile(runtimePath, password)
2222
if err != nil {
2323
return err
2424
}
@@ -50,8 +50,8 @@ func defaultInitDatabase(binaryExtractLocation, pgDataDir, username, password, l
5050
return nil
5151
}
5252

53-
func createPasswordFile(binaryExtractLocation, password string) (string, error) {
54-
passwordFileLocation := filepath.Join(binaryExtractLocation, "pwfile")
53+
func createPasswordFile(runtimePath, password string) (string, error) {
54+
passwordFileLocation := filepath.Join(runtimePath, "pwfile")
5555
if err := ioutil.WriteFile(passwordFileLocation, []byte(password), 0600); err != nil {
5656
return "", fmt.Errorf("unable to write password file to %s", passwordFileLocation)
5757
}

0 commit comments

Comments
 (0)