Repository: theupdateframework/go-tuf
Pull request #188 (2021-12-08 17:39:03)

expose `Close()` in `LocalStore` interface
This allows for package consumers to elegantly`Close()` the underlying datastore, avoiding memory leaks.

See also: https://github.com/sigstore/cosign/issues/1160

coveralls: ## Pull Request Test Coverage Report for [Build 1555237308](https://coveralls.io/builds/44862416)

* **0** of **3**   **(0.0%)**  changed or added relevant lines in **1** file are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.07%**) to **69.101%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [client/local_store.go](https://coveralls.io/builds/44862416/source?filename=client%2Flocal_store.go#L27) | 0 | 3 | 0.0%
<!-- | **Total:** | **0** | **3** | **0.0%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/44862416/badge)](https://coveralls.io/builds/44862416) |
| :-- | --: |
| Change from base [Build 1537057315](https://coveralls.io/builds/44752439): |  -0.07% |
| Covered Lines: | 2044 |
| Relevant Lines: | 2958 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/client/client.go b/client/client.go
index cfa2ddd..0459a1d 100644
--- a/client/client.go
+++ b/client/client.go
@@ -24,6 +24,8 @@ const (
 
 // LocalStore is local storage for downloaded top-level metadata.
 type LocalStore interface {
+	io.Closer
+
 	// GetMeta returns top-level metadata from local storage. The keys are
 	// in the form `ROLE.json`, with ROLE being a valid top-level role.
 	GetMeta() (map[string]json.RawMessage, error)
diff --git a/client/leveldbstore/leveldbstore.go b/client/leveldbstore/leveldbstore.go
index 82a0d1a..578023a 100644
--- a/client/leveldbstore/leveldbstore.go
+++ b/client/leveldbstore/leveldbstore.go
@@ -45,12 +45,11 @@ func (f *fileLocalStore) DeleteMeta(name string) error {
 }
 
 func (f *fileLocalStore) Close() error {
-	if err := f.db.Close(); err != nil {
-		return err
+	// Always close both before returning any errors
+	dbCloseErr := f.db.Close()
+	fdCloseErr := f.fd.Close()
+	if dbCloseErr != nil {
+		return dbCloseErr
 	}
-	if err := f.fd.Close(); err != nil {
-		return err
-	}
-
-	return nil
+	return fdCloseErr
 }
diff --git a/client/local_store.go b/client/local_store.go
index 7e3ef15..bb9421f 100644
--- a/client/local_store.go
+++ b/client/local_store.go
@@ -23,3 +23,7 @@ func (m memoryLocalStore) DeleteMeta(name string) error {
 	delete(m, name)
 	return nil
 }
+
+func (m memoryLocalStore) Close() error {
+	return nil
+}


Test patch:
diff --git a/client/leveldbstore/leveldbstore_test.go b/client/leveldbstore/leveldbstore_test.go
index 847b6e9..1a11cb3 100644
--- a/client/leveldbstore/leveldbstore_test.go
+++ b/client/leveldbstore/leveldbstore_test.go
@@ -20,6 +20,7 @@ func (LocalStoreSuite) TestFileLocalStore(c *C) {
 	path := filepath.Join(tmp, "tuf.db")
 	store, err := FileLocalStore(path)
 	c.Assert(err, IsNil)
+	defer store.Close()
 
 	type meta map[string]json.RawMessage
 
@@ -43,9 +44,12 @@ func (LocalStoreSuite) TestFileLocalStore(c *C) {
 	assertGet(meta{"root.json": rootJSON, "targets.json": targetsJSON})
 
 	// a new store should get the same meta
-	c.Assert(store.(*fileLocalStore).Close(), IsNil)
+	c.Assert(store.Close(), IsNil)
 	store, err = FileLocalStore(path)
 	c.Assert(err, IsNil)
+	defer func() {
+		c.Assert(store.Close(), IsNil)
+	}()
 	assertGet(meta{"root.json": rootJSON, "targets.json": targetsJSON})
 }
 
diff --git a/client/local_store_test.go b/client/local_store_test.go
index bbfaf3d..e2c1dfd 100644
--- a/client/local_store_test.go
+++ b/client/local_store_test.go
@@ -8,6 +8,9 @@ import (
 
 func TestDeleteMeta(t *testing.T) {
 	l := MemoryLocalStore()
+	defer func() {
+		assert.Equal(t, nil, l.Close())
+	}()
 	assert.Equal(t, l.SetMeta("root.json", []byte(`
   {
 	  "signed": {},