diff --git a/coverage-badge.svg b/coverage-badge.svg index 9b2fae3..2a6facc 100644 --- a/coverage-badge.svg +++ b/coverage-badge.svg @@ -11,7 +11,7 @@ coverage coverage - 14.4% - 14.4% + 19.2% + 19.2% diff --git a/internal/dl/dl.go b/internal/dl/dl.go index 3415e25..4a91fcb 100644 --- a/internal/dl/dl.go +++ b/internal/dl/dl.go @@ -42,9 +42,6 @@ import ( "golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2s" "golang.org/x/exp/slices" - - "gitea.plemya-x.ru/Plemya-x/ALR/internal/config" - "gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" ) // Константа для имени файла манифеста кэша @@ -83,6 +80,11 @@ func (t Type) String() string { return "" } +type DlCache interface { + Get(context.Context, string) (string, bool) + New(context.Context, string) (string, error) +} + // Структура Options содержит параметры для загрузки файлов и каталогов type Options struct { Hash []byte @@ -94,6 +96,7 @@ type Options struct { PostprocDisabled bool Progress io.Writer LocalDir string + DlCache DlCache } // Метод для создания нового хеша на основе указанного алгоритма хеширования @@ -145,9 +148,6 @@ type UpdatingDownloader interface { // Функция Download загружает файл или каталог с использованием указанных параметров func Download(ctx context.Context, opts Options) (err error) { - cfg := config.GetInstance(ctx) - dc := dlcache.New(cfg) - normalized, err := normalizeURL(opts.URL) if err != nil { return err @@ -162,7 +162,7 @@ func Download(ctx context.Context, opts Options) (err error) { } var t Type - cacheDir, ok := dc.Get(ctx, opts.URL) + cacheDir, ok := opts.DlCache.Get(ctx, opts.URL) if ok { var updated bool if d, ok := d.(UpdatingDownloader); ok { @@ -221,7 +221,7 @@ func Download(ctx context.Context, opts Options) (err error) { slog.Info(gotext.Get("Downloading source"), "source", opts.Name, "downloader", d.Name()) - cacheDir, err = dc.New(ctx, opts.URL) + cacheDir, err = opts.DlCache.New(ctx, opts.URL) if err != nil { return err } diff --git a/internal/dl/dl_test.go b/internal/dl/dl_test.go new file mode 100644 index 0000000..317c457 --- /dev/null +++ b/internal/dl/dl_test.go @@ -0,0 +1,176 @@ +// ALR - Any Linux Repository +// Copyright (C) 2025 Евгений Храмов +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package dl_test + +import ( + "context" + "fmt" + "log" + "net/http" + "net/http/httptest" + "net/http/httputil" + "net/url" + "os" + "path" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "gitea.plemya-x.ru/Plemya-x/ALR/internal/config" + "gitea.plemya-x.ru/Plemya-x/ALR/internal/dl" + "gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" +) + +type TestALRConfig struct{} + +func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths { + return &config.Paths{ + CacheDir: "/tmp", + } +} + +func TestDownloadWithoutCache(t *testing.T) { + type testCase struct { + name string + path string + expected func(*testing.T, error, string) + } + + prepareServer := func() *httptest.Server { + // URL вашего Git-сервера + gitServerURL, err := url.Parse("https://gitea.plemya-x.ru") + if err != nil { + log.Fatalf("Failed to parse git server URL: %v", err) + } + + proxy := httputil.NewSingleHostReverseProxy(gitServerURL) + + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.URL.Path == "/file-downloader/file": + w.WriteHeader(http.StatusOK) + w.Write([]byte("Hello, World!")) + case strings.HasPrefix(r.URL.Path, "/git-downloader/git"): + r.URL.Host = gitServerURL.Host + r.URL.Scheme = gitServerURL.Scheme + r.Host = gitServerURL.Host + r.URL.Path, _ = strings.CutPrefix(r.URL.Path, "/git-downloader/git") + + proxy.ServeHTTP(w, r) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + } + + for _, tc := range []testCase{ + { + name: "simple file download", + path: "%s/file-downloader/file", + expected: func(t *testing.T, err error, tmpdir string) { + assert.NoError(t, err) + + _, err = os.Stat(path.Join(tmpdir, "file")) + assert.NoError(t, err) + }, + }, + { + name: "git download", + path: "git+%s/git-downloader/git/Plemya-x/xpamych-alr-repo", + expected: func(t *testing.T, err error, tmpdir string) { + assert.NoError(t, err) + + _, err = os.Stat(path.Join(tmpdir, "alr-repo.toml")) + assert.NoError(t, err) + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + server := prepareServer() + defer server.Close() + + tmpdir, err := os.MkdirTemp("", "test-download") + assert.NoError(t, err) + defer os.RemoveAll(tmpdir) + + opts := dl.Options{ + CacheDisabled: true, + URL: fmt.Sprintf(tc.path, server.URL), + Destination: tmpdir, + } + + err = dl.Download(context.Background(), opts) + + tc.expected(t, err, tmpdir) + }) + } +} + +func TestDownloadFileWithCache(t *testing.T) { + type testCase struct { + name string + } + + for _, tc := range []testCase{ + { + name: "simple download", + }, + } { + t.Run(tc.name, func(t *testing.T) { + called := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.URL.Path == "/file": + called += 1 + w.WriteHeader(http.StatusOK) + w.Write([]byte("Hello, World!")) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + defer server.Close() + + tmpdir, err := os.MkdirTemp("", "test-download") + assert.NoError(t, err) + defer os.RemoveAll(tmpdir) + + cfg := &TestALRConfig{} + + opts := dl.Options{ + CacheDisabled: false, + URL: server.URL + "/file", + Destination: tmpdir, + DlCache: dlcache.New(cfg), + } + + outputFile := path.Join(tmpdir, "file") + + err = dl.Download(context.Background(), opts) + assert.NoError(t, err) + _, err = os.Stat(outputFile) + assert.NoError(t, err) + + err = os.Remove(outputFile) + assert.NoError(t, err) + + err = dl.Download(context.Background(), opts) + assert.NoError(t, err) + assert.Equal(t, 1, called) + }) + } +} diff --git a/internal/dl/file.go b/internal/dl/file.go index 56405dd..2e63b39 100644 --- a/internal/dl/file.go +++ b/internal/dl/file.go @@ -143,7 +143,6 @@ func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string, return 0, "", err } r.Close() - out.Close() // Проверка контрольной суммы if opts.Hash != nil { diff --git a/internal/shutils/decoder/decoder.go b/internal/shutils/decoder/decoder.go index 906133d..629e2a7 100644 --- a/internal/shutils/decoder/decoder.go +++ b/internal/shutils/decoder/decoder.go @@ -169,21 +169,7 @@ type ScriptFunc func(ctx context.Context, opts ...interp.RunnerOption) error // GetFunc returns a function corresponding to a bash function // with the given name func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) { - fn := d.getFunc(name) - if fn == nil { - return nil, false - } - - return func(ctx context.Context, opts ...interp.RunnerOption) error { - sub := d.Runner.Subshell() - for _, opt := range opts { - err := opt(sub) - if err != nil { - return err - } - } - return sub.Run(ctx, fn) - }, true + return d.GetFuncP(name, nil) } type PrepareFunc func(context.Context, *interp.Runner) error diff --git a/internal/translations/default.pot b/internal/translations/default.pot index 28de0ef..b0cc59f 100644 --- a/internal/translations/default.pot +++ b/internal/translations/default.pot @@ -293,81 +293,81 @@ msgstr "" msgid "Error while running app" msgstr "" -#: pkg/build/build.go:107 +#: pkg/build/build.go:108 msgid "Failed to prompt user to view build script" msgstr "" -#: pkg/build/build.go:111 +#: pkg/build/build.go:112 msgid "Building package" msgstr "" -#: pkg/build/build.go:155 +#: pkg/build/build.go:156 msgid "Downloading sources" msgstr "" -#: pkg/build/build.go:167 +#: pkg/build/build.go:168 msgid "Building package metadata" msgstr "" -#: pkg/build/build.go:189 +#: pkg/build/build.go:190 msgid "Compressing package" msgstr "" -#: pkg/build/build.go:315 +#: pkg/build/build.go:316 msgid "" "Your system's CPU architecture doesn't match this package. Do you want to " "build anyway?" msgstr "" -#: pkg/build/build.go:326 +#: pkg/build/build.go:327 msgid "This package is already installed" msgstr "" -#: pkg/build/build.go:354 +#: pkg/build/build.go:355 msgid "Installing build dependencies" msgstr "" -#: pkg/build/build.go:396 +#: pkg/build/build.go:397 msgid "Installing dependencies" msgstr "" -#: pkg/build/build.go:442 +#: pkg/build/build.go:443 msgid "Executing version()" msgstr "" -#: pkg/build/build.go:462 +#: pkg/build/build.go:463 msgid "Updating version" msgstr "" -#: pkg/build/build.go:467 +#: pkg/build/build.go:468 msgid "Executing prepare()" msgstr "" -#: pkg/build/build.go:477 +#: pkg/build/build.go:478 msgid "Executing build()" msgstr "" -#: pkg/build/build.go:487 +#: pkg/build/build.go:488 msgid "Executing package()" msgstr "" -#: pkg/build/build.go:509 +#: pkg/build/build.go:510 msgid "Executing files()" msgstr "" -#: pkg/build/build.go:587 +#: pkg/build/build.go:588 msgid "AutoProv is not implemented for this package format, so it's skipped" msgstr "" -#: pkg/build/build.go:598 +#: pkg/build/build.go:599 msgid "AutoReq is not implemented for this package format, so it's skipped" msgstr "" -#: pkg/build/build.go:705 +#: pkg/build/build.go:706 msgid "Would you like to remove the build dependencies?" msgstr "" -#: pkg/build/build.go:811 +#: pkg/build/build.go:812 msgid "The checksums array must be the same length as sources" msgstr "" diff --git a/internal/translations/po/ru/default.po b/internal/translations/po/ru/default.po index fde74bf..3d5e548 100644 --- a/internal/translations/po/ru/default.po +++ b/internal/translations/po/ru/default.po @@ -307,27 +307,27 @@ msgstr "" msgid "Error while running app" msgstr "Ошибка при запуске приложения" -#: pkg/build/build.go:107 +#: pkg/build/build.go:108 msgid "Failed to prompt user to view build script" msgstr "Не удалось предложить пользователю просмотреть скрипт сборки" -#: pkg/build/build.go:111 +#: pkg/build/build.go:112 msgid "Building package" msgstr "Сборка пакета" -#: pkg/build/build.go:155 +#: pkg/build/build.go:156 msgid "Downloading sources" msgstr "Скачивание источников" -#: pkg/build/build.go:167 +#: pkg/build/build.go:168 msgid "Building package metadata" msgstr "Сборка метаданных пакета" -#: pkg/build/build.go:189 +#: pkg/build/build.go:190 msgid "Compressing package" msgstr "Сжатие пакета" -#: pkg/build/build.go:315 +#: pkg/build/build.go:316 msgid "" "Your system's CPU architecture doesn't match this package. Do you want to " "build anyway?" @@ -335,57 +335,57 @@ msgstr "" "Архитектура процессора вашей системы не соответствует этому пакету. Вы все " "равно хотите выполнить сборку?" -#: pkg/build/build.go:326 +#: pkg/build/build.go:327 msgid "This package is already installed" msgstr "Этот пакет уже установлен" -#: pkg/build/build.go:354 +#: pkg/build/build.go:355 msgid "Installing build dependencies" msgstr "Установка зависимостей сборки" -#: pkg/build/build.go:396 +#: pkg/build/build.go:397 msgid "Installing dependencies" msgstr "Установка зависимостей" -#: pkg/build/build.go:442 +#: pkg/build/build.go:443 msgid "Executing version()" msgstr "Исполнение версия()" -#: pkg/build/build.go:462 +#: pkg/build/build.go:463 msgid "Updating version" msgstr "Обновление версии" -#: pkg/build/build.go:467 +#: pkg/build/build.go:468 msgid "Executing prepare()" msgstr "Исполнение prepare()" -#: pkg/build/build.go:477 +#: pkg/build/build.go:478 msgid "Executing build()" msgstr "Исполнение build()" -#: pkg/build/build.go:487 +#: pkg/build/build.go:488 msgid "Executing package()" msgstr "Исполнение package()" -#: pkg/build/build.go:509 +#: pkg/build/build.go:510 msgid "Executing files()" msgstr "Исполнение files()" -#: pkg/build/build.go:587 +#: pkg/build/build.go:588 msgid "AutoProv is not implemented for this package format, so it's skipped" msgstr "" "AutoProv не реализовано для этого формата пакета, поэтому будет пропущено" -#: pkg/build/build.go:598 +#: pkg/build/build.go:599 msgid "AutoReq is not implemented for this package format, so it's skipped" msgstr "" "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" -#: pkg/build/build.go:705 +#: pkg/build/build.go:706 msgid "Would you like to remove the build dependencies?" msgstr "Хотели бы вы удалить зависимости сборки?" -#: pkg/build/build.go:811 +#: pkg/build/build.go:812 msgid "The checksums array must be the same length as sources" msgstr "Массив контрольных сумм должен быть той же длины, что и источники" diff --git a/pkg/build/build.go b/pkg/build/build.go index 9a30e1a..62be0a0 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -54,6 +54,7 @@ import ( "gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/dl" + "gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" "gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" "gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder" "gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" @@ -842,6 +843,9 @@ func getSources(ctx context.Context, dirs types.Directories, bv *types.BuildVars } } + cfg := config.GetInstance(ctx) + opts.DlCache = dlcache.New(cfg) + err := dl.Download(ctx, opts) if err != nil { return err