Преглед на файлове

SVI Добавление кода, тестов; 91.1%

SVI преди 9 месеца
родител
ревизия
a6f77c6e13

+ 22 - 0
internal/serv_http/serv_http.go

@@ -3,7 +3,9 @@ package serv_http
 
 import (
 	"fmt"
+	"log"
 	"os"
+	"sync"
 	"time"
 
 	"github.com/gofiber/fiber/v2"
@@ -23,6 +25,8 @@ type ServHttp struct {
 	port     string
 	store    types.IStore
 	anonym   *router_anonym.RouterAnonym
+	block    sync.RWMutex
+	isWork   bool // Признак работы
 }
 
 // NewServHttp -- возвращает новый HTTP-сервер
@@ -123,6 +127,9 @@ func NewServHttp(serv types.IService) (*ServHttp, error) {
 		return nil, err
 	}
 	sf.fiberApp.Get("/", sf.get)
+	sf.serv.Wg().Add(1)
+	sf.isWork = true
+	go sf.close()
 	return sf, nil
 }
 
@@ -150,3 +157,18 @@ func (sf *ServHttp) Run() error {
 	}
 	return nil
 }
+
+func (sf *ServHttp) close() {
+	<-sf.serv.Ctx().Done()
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if !sf.isWork {
+		return
+	}
+	sf.isWork = false
+	err := sf.fiberApp.Shutdown()
+	if err != nil {
+		log.Printf("ServHttp.close(): in shutdown fiberApp, err=\n\t%v\n", err)
+	}
+	sf.serv.Wg().Done()
+}

+ 28 - 0
internal/serv_http/serv_http_test.go

@@ -1,11 +1,13 @@
 package serv_http
 
 import (
+	"net/http"
 	"os"
 	"testing"
 	"time"
 
 	"git.p78su.freemyip.com/svi/gostore/pkg/mock/mock_service"
+	"github.com/gofiber/fiber/v2"
 )
 
 type tester struct {
@@ -19,6 +21,32 @@ func TestServHttp(t *testing.T) {
 		t: t,
 	}
 	sf.create()
+	sf.get()
+	sf.close()
+}
+
+// Закрытие сервиса
+func (sf *tester) close() {
+	sf.t.Log("close")
+	sf.serv.CancelApp()
+	sf.serv.Wg().Wait()
+	sf.web.close()
+}
+
+// Получение индексной страницы
+func (sf *tester) get() {
+	sf.t.Log("get")
+	app := fiber.New()
+	app.Get("/", sf.web.get)
+	req, err := http.NewRequest("GET", "/", nil)
+	if err != nil {
+		sf.t.Fatalf("get(): err=\n\t%v", err)
+	}
+	resp, err := app.Test(req)
+	if err != nil {
+		sf.t.Fatalf("get(): err=\n\t%v", err)
+	}
+	defer resp.Body.Close()
 }
 
 // Создание нового веб-сервера

+ 9 - 1
internal/service/service.go

@@ -5,6 +5,7 @@ import (
 	"context"
 	"fmt"
 	"log"
+	"sync"
 
 	"git.p78su.freemyip.com/svi/gostore/internal/serv_http"
 	"git.p78su.freemyip.com/svi/gostore/internal/store"
@@ -22,6 +23,7 @@ type Service struct {
 	ctxBg    context.Context // Неотменяемый контекст
 	ctx      context.Context // Контекст приложения
 	fnCancel func()          // Функция отмены контекста
+	wg       *sync.WaitGroup // Ожидатель потоков сервиса
 }
 
 // NewService -- возвращает новый объект сервиса
@@ -32,6 +34,7 @@ func NewService() (types.IService, error) {
 		ctxBg:    ctxBg,
 		ctx:      ctx,
 		fnCancel: fnCancel,
+		wg:       &sync.WaitGroup{},
 	}
 	var err error
 	sf.disk, err = store.NewStore(sf)
@@ -65,6 +68,11 @@ func (sf *Service) Ctx() context.Context {
 	return sf.ctx
 }
 
+// Wg -- возвращает ожидатель группы потоков
+func (sf *Service) Wg() *sync.WaitGroup {
+	return sf.wg
+}
+
 // Run -- запускает сервис в работу
 func (sf *Service) Run() error {
 	go func() {
@@ -73,6 +81,6 @@ func (sf *Service) Run() error {
 			log.Printf("Service.Run(): in run IServHttp, err=\n\t%v\n", err)
 		}
 	}()
-	<-sf.ctx.Done()
+	sf.wg.Wait()
 	return nil
 }

+ 78 - 0
internal/service/service_test.go

@@ -1 +1,79 @@
 package service
+
+import (
+	"os"
+	"testing"
+
+	"git.p78su.freemyip.com/svi/gostore/internal/store"
+)
+
+type tester struct {
+	t *testing.T
+}
+
+func TestService(t *testing.T) {
+	fnClear := func() {
+		_ = os.RemoveAll("./store")
+	}
+	fnClear()
+	defer fnClear()
+	sf := &tester{
+		t: t,
+	}
+	sf.create()
+}
+
+// create -- создает сервис
+func (sf *tester) create() {
+	sf.t.Log("create")
+	sf.createBad1()
+	sf.createBad2()
+	sf.createGood1()
+}
+
+func (sf *tester) createGood1() {
+	sf.t.Log("createGood1")
+	_ = os.RemoveAll("./store")
+	os.Unsetenv("HTTP_PORT")
+	os.Setenv("HTTP_PORT", "18080")
+	serv, err := NewService()
+	if err != nil {
+		sf.t.Fatalf("createGood1(): in create IStoreDisk, err=\n\t%v", err)
+	}
+	if serv == nil {
+		sf.t.Fatalf("createGood1(): serv==nil")
+	}
+	if http := serv.ServHttp(); http == nil {
+		sf.t.Fatalf("createGood1(): http==nil")
+	}
+	serv.CancelApp()
+	if err := serv.Run(); err != nil {
+		sf.t.Fatalf("createGood1(): err=\n\t%v", err)
+	}
+}
+
+// Нет переменной окружения для порта веб-сервера
+func (sf *tester) createBad2() {
+	sf.t.Log("createBad2")
+	serv, err := NewService()
+	if err == nil {
+		sf.t.Fatalf("createBad2(): err==nil")
+	}
+	if serv != nil {
+		sf.t.Fatalf("createBad2(): serv!=nil")
+	}
+}
+
+// Не удалось создать хранилище
+func (sf *tester) createBad1() {
+	sf.t.Log("createBad1")
+	store.IsBad_ = true
+	serv, err := NewService()
+	if err == nil {
+		sf.t.Fatalf("createBad1(): err==nil")
+	}
+	if serv != nil {
+		sf.t.Fatalf("createBad1(): serv!=nil")
+	}
+	store.IsBad_ = false
+}

+ 43 - 3
internal/store/store.go

@@ -3,8 +3,10 @@ package store
 
 import (
 	"fmt"
+	"log"
 	"os"
 	"strings"
+	"sync"
 
 	"github.com/syndtr/goleveldb/leveldb"
 	"github.com/syndtr/goleveldb/leveldb/util"
@@ -14,12 +16,19 @@ import (
 
 // StoreDisk -- хранилище на диске
 type Store struct {
-	serv types.IService
-	db   *leveldb.DB
+	serv   types.IService
+	db     *leveldb.DB
+	isWork bool
+	block  sync.RWMutex
 }
 
+var IsBad_ = false // Для тестов
+
 // NewStore -- возвращает новое хранилище на диске
 func NewStore(serv types.IService) (*Store, error) {
+	if IsBad_ {
+		return nil, fmt.Errorf("NewStore(): IsBad_==true")
+	}
 	if serv == nil {
 		return nil, fmt.Errorf("NewStore(): IService==nil")
 	}
@@ -38,12 +47,24 @@ func NewStore(serv types.IService) (*Store, error) {
 		serv: serv,
 		db:   db,
 	}
+	sf.serv.Wg().Add(1)
+	sf.isWork = true
 	go sf.close()
 	return sf, nil
 }
 
+// Проверка на нерабочую схему
+func (sf *Store) isClosed() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return !sf.isWork
+}
+
 // Put -- размещает в хранилище ключ и значение
 func (sf *Store) Put(key string, val []byte) error {
+	if sf.isClosed() {
+		return fmt.Errorf("Store.Put(): store is closed")
+	}
 	if key == "" {
 		return fmt.Errorf("Store.Put(): key is empty")
 	}
@@ -56,6 +77,9 @@ func (sf *Store) Put(key string, val []byte) error {
 
 // Get -- возвращает значение ключа
 func (sf *Store) Get(key string) ([]byte, error) {
+	if sf.isClosed() {
+		return nil, fmt.Errorf("Store.Get(): store is closed")
+	}
 	val, err := sf.db.Get([]byte(key), nil)
 	if err != nil {
 		return nil, fmt.Errorf("Store.Get(): key=%q\terr=\n\t%w", key, err)
@@ -65,6 +89,9 @@ func (sf *Store) Get(key string) ([]byte, error) {
 
 // Find -- ищет ключи по префиксу
 func (sf *Store) Find(prefixKey string) ([]string, error) {
+	if sf.isClosed() {
+		return nil, fmt.Errorf("Store.Find(): store is closed")
+	}
 	lstKey := []string{}
 	iter := sf.db.NewIterator(util.BytesPrefix([]byte(prefixKey)), nil)
 	for iter.Next() {
@@ -81,6 +108,9 @@ func (sf *Store) Find(prefixKey string) ([]string, error) {
 
 // Del -- удаление ключа из базы
 func (sf *Store) Del(key string) error {
+	if sf.isClosed() {
+		return fmt.Errorf("Store.Del(): store is closed")
+	}
 	err := sf.db.Delete([]byte(key), nil)
 	if err != nil {
 		return fmt.Errorf("Store.Del(): key=%q\terr=\n\t%w", key, err)
@@ -91,5 +121,15 @@ func (sf *Store) Del(key string) error {
 // Ожидание закрытия приложения
 func (sf *Store) close() {
 	<-sf.serv.Ctx().Done()
-	_ = sf.db.Close()
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if !sf.isWork {
+		return
+	}
+	sf.isWork = false
+	err := sf.db.Close()
+	if err != nil {
+		log.Printf("Store.close(): err=\n\t%v\n", err)
+	}
+	sf.serv.Wg().Done()
 }

+ 23 - 1
internal/store/store_test.go

@@ -48,6 +48,13 @@ func (sf *tester) close() {
 	if err := sf.store.Put("key2", []byte("val")); err == nil {
 		sf.t.Fatalf("close(): err==nil")
 	}
+	binVal, err := sf.store.Get("key2")
+	if err == nil {
+		sf.t.Fatalf("close(): err==nil")
+	}
+	if binVal != nil {
+		sf.t.Fatalf("close(): binVal!=nil")
+	}
 }
 
 // Удалет ключ из хранилища
@@ -129,12 +136,12 @@ func (sf *tester) putBad1() {
 func (sf *tester) create() {
 	sf.t.Log("create")
 	sf.createBad1()
+	sf.createBad2()
 	sf.createGood1()
 }
 
 func (sf *tester) createGood1() {
 	sf.t.Log("createGood1")
-	sf.serv = mock_service.NewMockService()
 	var err error
 	sf.store, err = NewStore(sf.serv)
 	if err != nil {
@@ -145,6 +152,21 @@ func (sf *tester) createGood1() {
 	}
 }
 
+// Испорчена переменная модуля
+func (sf *tester) createBad2() {
+	sf.t.Log("createBad2")
+	sf.serv = mock_service.NewMockService()
+	IsBad_ = true
+	store, err := NewStore(sf.serv)
+	if err == nil {
+		sf.t.Fatalf("createBad2(): err==nil")
+	}
+	if store != nil {
+		sf.t.Fatalf("createBad2(): store!=nil")
+	}
+	IsBad_ = false
+}
+
 // createBad1 -- нет объекта сервиса
 func (sf *tester) createBad1() {
 	sf.t.Log("createBad1")

+ 8 - 0
pkg/mock/mock_service/mock_service.go

@@ -3,6 +3,7 @@ package mock_service
 
 import (
 	"context"
+	"sync"
 
 	"git.p78su.freemyip.com/svi/gostore/pkg/types"
 )
@@ -13,6 +14,7 @@ type MockService struct {
 	ctx       context.Context
 	ServHttp_ types.IServHttp
 	Store_    types.IStore
+	Wg_       *sync.WaitGroup
 }
 
 // NewMockService -- возвращает новый мок-сервис
@@ -22,10 +24,16 @@ func NewMockService() *MockService {
 	sf := &MockService{
 		ctx:      ctx,
 		fnCancel: fnCancel,
+		Wg_:      &sync.WaitGroup{},
 	}
 	return sf
 }
 
+// Wg -- возвращает ожидатель группы потоков
+func (sf *MockService) Wg() *sync.WaitGroup {
+	return sf.Wg_
+}
+
 // Store -- возвращает хранилище
 func (sf *MockService) Store() types.IStore {
 	return sf.Store_

+ 1 - 0
pkg/mock/mock_service/mock_service_test.go

@@ -42,4 +42,5 @@ func (sf *tester) create() {
 		sf.t.Fatalf("create(): ctx==nil")
 	}
 	sf.serv.CancelApp()
+	sf.serv.Wg().Wait()
 }

+ 6 - 1
pkg/types/iservice.go

@@ -1,7 +1,10 @@
 // package types -- содержит интерфейсы проекта
 package types
 
-import "context"
+import (
+	"context"
+	"sync"
+)
 
 // IService -- объект сервиса
 type IService interface {
@@ -11,6 +14,8 @@ type IService interface {
 	Ctx() context.Context
 	// CancelApp -- отменяет контекст приложения
 	CancelApp()
+	// Wg -- возвращает ожидатель группы потоков
+	Wg() *sync.WaitGroup
 	// Store -- хранилище
 	Store() IStore
 	// ServHttp -- HTTP-сервер