From 082e970112233fc92a3d12cd3e7e00dac9124ec2 Mon Sep 17 00:00:00 2001 From: matheusgomes28 Date: Sat, 24 Feb 2024 16:14:19 +0000 Subject: [PATCH] Adding Simple Gin Unit Tests (#24) Add a very basic gin test example where we get the index page and check it contains a particular post in the mock database. To complete this, I had to mock the database by turning the previous declaration into an interface, and moving the concrete types into `SqlDatabase`. The mock is done in `DatabaseMock`. --- .github/workflows/pipeline.yml | 8 +-- .github/workflows/test.yml | 24 +++++++++ admin-app/app.go | 28 ++++------ app/app.go | 21 +++----- app/cache.go | 45 +++++++++++----- app/cache_test.go | 95 ++++++++++++++++++++++++++++++++++ app/contact.go | 2 +- app/post.go | 2 +- cmd/urchin-admin/main.go | 11 +++- cmd/urchin/index_test.go | 64 +++++++++++++++++++++++ cmd/urchin/main.go | 11 +++- common/app_settings.go | 11 ++-- database/database.go | 26 ++++++---- go.mod | 5 +- go.sum | 3 +- 15 files changed, 288 insertions(+), 68 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 app/cache_test.go create mode 100644 cmd/urchin/index_test.go diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index e46c48c..27a6ad6 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -13,9 +13,11 @@ jobs: build: uses: ./.github/workflows/build.yml needs: failfast - # tests: - # uses: ./.github/workflows/test.yml - # needs: build + + tests: + uses: ./.github/workflows/test.yml + needs: build + # release: # uses: ./.github/workflows/release.yml # needs: tests diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b10e332 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,24 @@ +name: Tests + +on: [workflow_call] + +jobs: + linters: + name: Tests 🧪 + + runs-on: ubuntu-latest + container: + image: mattgomes28/urchin-golang:0.2 + options: --user 1001 + + steps: + - uses: actions/checkout@v3 + + - name: Generating templ files + run: | + templ generate + shell: bash + + - name: Running Go Tests 🧪 + run: | + go test ./... -v diff --git a/admin-app/app.go b/admin-app/app.go index adf65d8..ec23aa6 100644 --- a/admin-app/app.go +++ b/admin-app/app.go @@ -2,7 +2,6 @@ package admin_app import ( "encoding/json" - "fmt" "net/http" "strconv" @@ -33,7 +32,7 @@ type DeletePostRequest struct { Id int `json:"id"` } -func getPostHandler(database *database.Database) func(*gin.Context) { +func getPostHandler(database database.Database) func(*gin.Context) { return func(c *gin.Context) { // localhost:8080/post/{id} var post_binding PostBinding @@ -73,7 +72,7 @@ func getPostHandler(database *database.Database) func(*gin.Context) { } } -func postPostHandler(database *database.Database) func(*gin.Context) { +func postPostHandler(database database.Database) func(*gin.Context) { return func(c *gin.Context) { var add_post_request AddPostRequest decoder := json.NewDecoder(c.Request.Body) @@ -108,7 +107,7 @@ func postPostHandler(database *database.Database) func(*gin.Context) { } } -func putPostHandler(database *database.Database) func(*gin.Context) { +func putPostHandler(database database.Database) func(*gin.Context) { return func(c *gin.Context) { var change_post_request ChangePostRequest decoder := json.NewDecoder(c.Request.Body) @@ -145,7 +144,7 @@ func putPostHandler(database *database.Database) func(*gin.Context) { } } -func deletePostHandler(database *database.Database) func(*gin.Context) { +func deletePostHandler(database database.Database) func(*gin.Context) { return func(c *gin.Context) { var delete_post_request DeletePostRequest decoder := json.NewDecoder(c.Request.Body) @@ -177,21 +176,14 @@ func deletePostHandler(database *database.Database) func(*gin.Context) { } } -func Run(app_settings common.AppSettings, database database.Database) error { +func SetupRoutes(app_settings common.AppSettings, database database.Database) *gin.Engine { r := gin.Default() r.MaxMultipartMemory = 1 - r.GET("/posts/:id", getPostHandler(&database)) - r.POST("/posts", postPostHandler(&database)) - r.PUT("/posts", putPostHandler(&database)) - r.DELETE("/posts", deletePostHandler(&database)) - - err := r.Run(fmt.Sprintf(":%s", app_settings.WebserverPort)) - if err != nil { - log.Error().Msgf("could not run app: %v", err) - return err - } - - return nil + r.GET("/posts/:id", getPostHandler(database)) + r.POST("/posts", postPostHandler(database)) + r.PUT("/posts", putPostHandler(database)) + r.DELETE("/posts", deletePostHandler(database)) + return r } diff --git a/app/app.go b/app/app.go index 3ddc312..1cf8664 100644 --- a/app/app.go +++ b/app/app.go @@ -2,7 +2,6 @@ package app import ( "bytes" - "fmt" "net/http" "time" @@ -15,9 +14,9 @@ import ( const CACHE_TIMEOUT = 20 * time.Second -type Generator = func(*gin.Context, common.AppSettings, *database.Database) ([]byte, error) +type Generator = func(*gin.Context, common.AppSettings, database.Database) ([]byte, error) -func Run(app_settings common.AppSettings, database *database.Database) error { +func SetupRoutes(app_settings common.AppSettings, database database.Database) *gin.Engine { r := gin.Default() r.MaxMultipartMemory = 1 @@ -31,20 +30,14 @@ func Run(app_settings common.AppSettings, database *database.Database) error { r.POST("/contact-send", makeContactFormHandler()) r.Static("/static", "./static") - err := r.Run(fmt.Sprintf(":%s", app_settings.WebserverPort)) - if err != nil { - log.Error().Msgf("could not run app: %v", err) - return err - } - - return nil + return r } -func addCachableHandler(e *gin.Engine, method string, endpoint string, generator Generator, cache *Cache, app_settings common.AppSettings, db *database.Database) { +func addCachableHandler(e *gin.Engine, method string, endpoint string, generator Generator, cache *Cache, app_settings common.AppSettings, db database.Database) { handler := func(c *gin.Context) { // if the endpoint is cached - cached_endpoint, err := cache.Get(c.Request.RequestURI) + cached_endpoint, err := (*cache).Get(c.Request.RequestURI) if err == nil { c.Data(http.StatusOK, "text/html; charset=utf-8", cached_endpoint.contents) return @@ -57,7 +50,7 @@ func addCachableHandler(e *gin.Engine, method string, endpoint string, generator } // After handler (add to cache) - err = cache.Store(c.Request.RequestURI, html_buffer) + err = (*cache).Store(c.Request.RequestURI, html_buffer) if err != nil { log.Warn().Msgf("could not add page to cache: %v", err) } @@ -81,7 +74,7 @@ func addCachableHandler(e *gin.Engine, method string, endpoint string, generator // / This function will act as the handler for // / the home page -func homeHandler(c *gin.Context, settings common.AppSettings, db *database.Database) ([]byte, error) { +func homeHandler(c *gin.Context, settings common.AppSettings, db database.Database) ([]byte, error) { posts, err := db.GetPosts() if err != nil { return nil, err diff --git a/app/cache.go b/app/cache.go index b1e395a..bdd4a49 100644 --- a/app/cache.go +++ b/app/cache.go @@ -22,15 +22,33 @@ func emptyEndpointCache() EndpointCache { return EndpointCache{"", []byte{}, time.Now()} } -type Cache struct { +type Cache interface { + Get(name string) (EndpointCache, error) + Store(name string, buffer []byte) error + Size() uint64 +} + +type CacheValidator interface { + IsValid(cache *EndpointCache) bool +} + +type TimeValidator struct{} + +func (validator *TimeValidator) IsValid(cache *EndpointCache) bool { + // We only return the cache if it's still valid + return cache.validUntil.After(time.Now()) +} + +type TimedCache struct { cacheMap shardedmap.ShardMap cacheTimeout time.Duration estimatedSize atomic.Uint64 // in bytes + validator CacheValidator } -func (self *Cache) Store(name string, buffer []byte) error { +func (cache *TimedCache) Store(name string, buffer []byte) error { // Only store to the cache if we have enough space left - afterSizeMB := float64(self.estimatedSize.Load()+uint64(len(buffer))) / 1000000 + afterSizeMB := float64(cache.estimatedSize.Load()+uint64(len(buffer))) / 1000000 if afterSizeMB > MAX_CACHE_SIZE_MB { return fmt.Errorf("maximum size reached") } @@ -38,24 +56,24 @@ func (self *Cache) Store(name string, buffer []byte) error { var cache_entry interface{} = EndpointCache{ name: name, contents: buffer, - validUntil: time.Now().Add(self.cacheTimeout), + validUntil: time.Now().Add(cache.cacheTimeout), } - self.cacheMap.Set(name, &cache_entry) - self.estimatedSize.Add(uint64(len(buffer))) + cache.cacheMap.Set(name, &cache_entry) + cache.estimatedSize.Add(uint64(len(buffer))) return nil } -func (self *Cache) Get(name string) (EndpointCache, error) { +func (cache *TimedCache) Get(name string) (EndpointCache, error) { // if the endpoint is cached - cached_entry := self.cacheMap.Get(name) + cached_entry := cache.cacheMap.Get(name) if cached_entry != nil { cache_contents := (*cached_entry).(EndpointCache) // We only return the cache if it's still valid - if cache_contents.validUntil.After(time.Now()) { + if cache.validator.IsValid(&cache_contents) { return cache_contents, nil } else { - self.cacheMap.Delete(name) + cache.cacheMap.Delete(name) return emptyEndpointCache(), fmt.Errorf("cached endpoint had expired") } } @@ -63,14 +81,15 @@ func (self *Cache) Get(name string) (EndpointCache, error) { return emptyEndpointCache(), fmt.Errorf("cache does not contain key") } -func (self *Cache) Size() uint64 { - return self.estimatedSize.Load() +func (cache *TimedCache) Size() uint64 { + return cache.estimatedSize.Load() } func makeCache(n_shards int, expiry_duration time.Duration) Cache { - return Cache{ + return &TimedCache{ cacheMap: shardedmap.NewShardMap(n_shards), cacheTimeout: expiry_duration, estimatedSize: atomic.Uint64{}, + validator: &TimeValidator{}, } } diff --git a/app/cache_test.go b/app/cache_test.go new file mode 100644 index 0000000..98192c4 --- /dev/null +++ b/app/cache_test.go @@ -0,0 +1,95 @@ +package app + +import ( + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + shardedmap "github.com/zutto/shardedmap" +) + +type TrueTimeMockValidator struct{} + +func (validator *TrueTimeMockValidator) IsValid(cache *EndpointCache) bool { + return true +} + +type FalseTimeMockValidator struct{} + +func (validator *FalseTimeMockValidator) IsValid(cache *EndpointCache) bool { + return false +} + +func makeTrueCacheMock() Cache { + return &TimedCache{ + cacheMap: shardedmap.NewShardMap(2), + cacheTimeout: 10 * time.Second, + estimatedSize: atomic.Uint64{}, + validator: &TrueTimeMockValidator{}, + } +} + +func makeFalseCacheMock() Cache { + return &TimedCache{ + cacheMap: shardedmap.NewShardMap(2), + cacheTimeout: 10 * time.Second, + estimatedSize: atomic.Uint64{}, + validator: &FalseTimeMockValidator{}, + } +} + +func TestCacheAddition(t *testing.T) { + + test_data := []struct { + name string + contents []byte + }{ + {"first", []byte("hello")}, + {"second", []byte("the quick brown fox does some weird stuff")}, + {"veryLonNameThatIsProbablyTooLong", []byte("Hello there my friends")}, + {"nPe9Rkff6ER6EzAxPUIpxc8UBBLm71hhq2MO9hkQWisrfihUqv", []byte("oA7Hv1A7vOuZSKrPT4ZN5DGKNSHZqpLEvUA5hu54CMyIt8c78u")}, + } + + cache := makeTrueCacheMock() + + rolling_size := uint64(0) + for _, test_case := range test_data { + + rolling_size += uint64(len(test_case.contents)) + err := cache.Store(test_case.name, test_case.contents) + assert.Nil(t, err) + assert.Equal(t, cache.Size(), rolling_size) + + endpoint_cache, err := cache.Get(test_case.name) + assert.Nil(t, err) + assert.Equal(t, endpoint_cache.contents, test_case.contents) + } +} + +func TestCacheFailure(t *testing.T) { + + test_data := []struct { + name string + contents []byte + }{ + {"first", []byte("hello")}, + {"second", []byte("the quick brown fox does some weird stuff")}, + {"veryLonNameThatIsProbablyTooLong", []byte("Hello there my friends")}, + {"nPe9Rkff6ER6EzAxPUIpxc8UBBLm71hhq2MO9hkQWisrfihUqv", []byte("oA7Hv1A7vOuZSKrPT4ZN5DGKNSHZqpLEvUA5hu54CMyIt8c78u")}, + } + + cache := makeFalseCacheMock() + + rolling_size := uint64(0) + for _, test_case := range test_data { + + rolling_size += uint64(len(test_case.contents)) + err := cache.Store(test_case.name, test_case.contents) + assert.Nil(t, err) + assert.Equal(t, cache.Size(), rolling_size) + + _, err = cache.Get(test_case.name) + assert.NotNil(t, err) + } +} diff --git a/app/contact.go b/app/contact.go index 7b85b63..a9cbfc5 100644 --- a/app/contact.go +++ b/app/contact.go @@ -58,7 +58,7 @@ func makeContactFormHandler() func(*gin.Context) { } // TODO : This is a duplicate of the index handler... abstract -func contactHandler(c *gin.Context, app_settings common.AppSettings, db *database.Database) ([]byte, error) { +func contactHandler(c *gin.Context, app_settings common.AppSettings, db database.Database) ([]byte, error) { index_view := views.MakeContactPage() html_buffer := bytes.NewBuffer(nil) if err := index_view.Render(c, html_buffer); err != nil { diff --git a/app/post.go b/app/post.go index 433c307..7eb6eef 100644 --- a/app/post.go +++ b/app/post.go @@ -32,7 +32,7 @@ func mdToHTML(md []byte) []byte { return markdown.Render(doc, renderer) } -func postHandler(c *gin.Context, app_settings common.AppSettings, database *database.Database) ([]byte, error) { +func postHandler(c *gin.Context, app_settings common.AppSettings, database database.Database) ([]byte, error) { // localhost:8080/post/{id} var post_binding PostBinding diff --git a/cmd/urchin-admin/main.go b/cmd/urchin-admin/main.go index b51c94e..5252ae0 100644 --- a/cmd/urchin-admin/main.go +++ b/cmd/urchin-admin/main.go @@ -1,6 +1,9 @@ package main import ( + "fmt" + "os" + _ "github.com/go-sql-driver/mysql" admin_app "github.com/matheusgomes28/urchin/admin-app" "github.com/matheusgomes28/urchin/common" @@ -17,6 +20,7 @@ func main() { app_settings, err := common.LoadSettings() if err != nil { log.Fatal().Msgf("could not load app settings: %v", err) + os.Exit(-1) } database, err := database.MakeSqlConnection( @@ -28,10 +32,13 @@ func main() { ) if err != nil { log.Fatal().Msgf("could not create database: %v", err) + os.Exit(-1) } - err = admin_app.Run(app_settings, database) + r := admin_app.SetupRoutes(app_settings, database) + err = r.Run(fmt.Sprintf(":%d", app_settings.WebserverPort)) if err != nil { - log.Fatal().Msgf("could not run app: %v", err) + log.Error().Msgf("could not run app: %v", err) + os.Exit(-1) } } diff --git a/cmd/urchin/index_test.go b/cmd/urchin/index_test.go new file mode 100644 index 0000000..b8ea617 --- /dev/null +++ b/cmd/urchin/index_test.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/matheusgomes28/urchin/app" + "github.com/matheusgomes28/urchin/common" + + "github.com/stretchr/testify/assert" +) + +type DatabaseMock struct{} + +func (db DatabaseMock) GetPosts() ([]common.Post, error) { + return []common.Post{ + { + Title: "TestPost", + Content: "TestContent", + Excerpt: "TestExcerpt", + Id: 0, + }, + }, nil +} + +func (db DatabaseMock) GetPost(post_id int) (common.Post, error) { + return common.Post{}, fmt.Errorf("not implemented") +} + +func (db DatabaseMock) AddPost(title string, excerpt string, content string) (int, error) { + return 0, fmt.Errorf("not implemented") +} + +func (db DatabaseMock) ChangePost(id int, title string, excerpt string, content string) error { + return nil +} + +func (db DatabaseMock) DeletePost(id int) error { + return fmt.Errorf("not implemented") +} + +func TestIndexPing(t *testing.T) { + app_settings := common.AppSettings{ + DatabaseAddress: "localhost", + DatabasePort: 3006, + DatabaseUser: "root", + DatabasePassword: "root", + DatabaseName: "urchin", + WebserverPort: 8080, + } + + database_mock := DatabaseMock{} + + r := app.SetupRoutes(app_settings, database_mock) + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + r.ServeHTTP(w, req) + + assert.Equal(t, 200, w.Code) + assert.Contains(t, w.Body.String(), "TestPost") + assert.Contains(t, w.Body.String(), "TestExcerpt") +} diff --git a/cmd/urchin/main.go b/cmd/urchin/main.go index 5335563..9878ea3 100644 --- a/cmd/urchin/main.go +++ b/cmd/urchin/main.go @@ -1,6 +1,9 @@ package main import ( + "fmt" + "os" + _ "github.com/go-sql-driver/mysql" "github.com/matheusgomes28/urchin/app" "github.com/matheusgomes28/urchin/common" @@ -13,7 +16,7 @@ func main() { app_settings, err := common.LoadSettings() if err != nil { log.Error().Msgf("could not get app settings: %v\n", err) - return + os.Exit(-1) } db_connection, err := database.MakeSqlConnection( @@ -25,9 +28,13 @@ func main() { ) if err != nil { log.Error().Msgf("could not create database connection: %v", err) + os.Exit(-1) } - if err = app.Run(app_settings, &db_connection); err != nil { + r := app.SetupRoutes(app_settings, db_connection) + err = r.Run(fmt.Sprintf(":%d", app_settings.WebserverPort)) + if err != nil { log.Error().Msgf("could not run app: %v", err) + os.Exit(-1) } } diff --git a/common/app_settings.go b/common/app_settings.go index 4f2b767..334537b 100644 --- a/common/app_settings.go +++ b/common/app_settings.go @@ -12,7 +12,7 @@ type AppSettings struct { DatabaseUser string DatabasePassword string DatabaseName string - WebserverPort string + WebserverPort int } func LoadSettings() (AppSettings, error) { @@ -47,11 +47,16 @@ func LoadSettings() (AppSettings, error) { return AppSettings{}, fmt.Errorf("URCHIN_DATABASE_PORT is not a valid integer: %v", err) } - webserver_port := os.Getenv("URCHIN_WEBSERVER_PORT") - if webserver_port == "" { + webserver_port_str := os.Getenv("URCHIN_WEBSERVER_PORT") + if webserver_port_str == "" { return AppSettings{}, fmt.Errorf("URCHIN_WEBSERVER_PORT is not defined") } + webserver_port, err := strconv.Atoi(webserver_port_str) + if err != nil { + return AppSettings{}, fmt.Errorf("URCHIN_WEBSERVER_PORT is not valid: %v", err) + } + return AppSettings{ DatabaseUser: database_user, DatabasePassword: database_password, diff --git a/database/database.go b/database/database.go index 7edf72d..755daad 100644 --- a/database/database.go +++ b/database/database.go @@ -10,7 +10,15 @@ import ( "github.com/rs/zerolog/log" ) -type Database struct { +type Database interface { + GetPosts() ([]common.Post, error) + GetPost(post_id int) (common.Post, error) + AddPost(title string, excerpt string, content string) (int, error) + ChangePost(id int, title string, excerpt string, content string) error + DeletePost(id int) error +} + +type SqlDatabase struct { Address string Port int User string @@ -19,7 +27,7 @@ type Database struct { // / This function gets all the posts from the current // / database connection. -func (db Database) GetPosts() ([]common.Post, error) { +func (db SqlDatabase) GetPosts() ([]common.Post, error) { rows, err := db.Connection.Query("SELECT title, excerpt, id FROM posts;") if err != nil { return make([]common.Post, 0), err @@ -40,7 +48,7 @@ func (db Database) GetPosts() ([]common.Post, error) { // / This function gets a post from the database // / with the given ID. -func (db *Database) GetPost(post_id int) (common.Post, error) { +func (db SqlDatabase) GetPost(post_id int) (common.Post, error) { rows, err := db.Connection.Query("SELECT title, content FROM posts WHERE id=?;", post_id) if err != nil { return common.Post{}, err @@ -57,7 +65,7 @@ func (db *Database) GetPost(post_id int) (common.Post, error) { } // / This function adds a post to the database -func (db *Database) AddPost(title string, excerpt string, content string) (int, error) { +func (db SqlDatabase) AddPost(title string, excerpt string, content string) (int, error) { res, err := db.Connection.Exec("INSERT INTO posts(content, title, excerpt) VALUES(?, ?, ?)", content, title, excerpt) if err != nil { return -1, err @@ -78,7 +86,7 @@ func (db *Database) AddPost(title string, excerpt string, content string) (int, // / This function changes a post based on the values // / provided. Note that empty strings will mean that // / the value will not be updated. -func (db *Database) ChangePost(id int, title string, excerpt string, content string) error { +func (db SqlDatabase) ChangePost(id int, title string, excerpt string, content string) error { tx, err := db.Connection.Begin() if err != nil { return err @@ -118,7 +126,7 @@ func (db *Database) ChangePost(id int, title string, excerpt string, content str // / This function changes a post based on the values // / provided. Note that empty strings will mean that // / the value will not be updated. -func (db *Database) DeletePost(id int) error { +func (db SqlDatabase) DeletePost(id int) error { if _, err := db.Connection.Exec("DELETE FROM posts WHERE id=?;", id); err != nil { return err } @@ -126,20 +134,20 @@ func (db *Database) DeletePost(id int) error { return nil } -func MakeSqlConnection(user string, password string, address string, port int, database string) (Database, error) { +func MakeSqlConnection(user string, password string, address string, port int, database string) (SqlDatabase, error) { /// Checking the DB connection /// TODO : let user specify the DB connection_str := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", user, password, address, port, database) db, err := sql.Open("mysql", connection_str) if err != nil { - return Database{}, err + return SqlDatabase{}, err } // See "Important settings" section. db.SetConnMaxLifetime(time.Second * 5) db.SetMaxOpenConns(10) db.SetMaxIdleConns(10) - return Database{ + return SqlDatabase{ Address: address, Port: port, User: user, diff --git a/go.mod b/go.mod index 5ad8dd5..b2af3bf 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/matheusgomes28/urchin -go 1.22 +go 1.22.0 require ( github.com/a-h/templ v0.2.543 @@ -8,12 +8,14 @@ require ( github.com/go-sql-driver/mysql v1.7.1 github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 github.com/rs/zerolog v1.31.0 + github.com/stretchr/testify v1.8.4 github.com/zutto/shardedmap v0.0.0-20180201164343-415202d0910e ) require ( github.com/bytedance/sonic v1.9.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -28,6 +30,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect golang.org/x/arch v0.3.0 // indirect diff --git a/go.sum b/go.sum index 04e3ac7..6c5e826 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=