Skip to content

Commit b99181b

Browse files
committed
feat: check validator and test improvement
1 parent 906b15e commit b99181b

File tree

7 files changed

+318
-97
lines changed

7 files changed

+318
-97
lines changed

config.example.json

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,28 @@
11
{
2-
"MWREL": "REL1_35",
3-
"Extensions": {
4-
"WMF": [
5-
"CookieWarning",
6-
"Math"
7-
],
8-
"Git": {
9-
"CrowdSec": {
10-
"type": "github",
11-
"repo": "ShinyColorsWiki/mediawiki-extension-crowdsec",
12-
"branch": "master"
13-
}
14-
},
15-
"http": {
16-
"AWS": "https://github.com/edwardspec/mediawiki-aws-s3/archive/refs/heads/master.zip"
17-
}
2+
"MWREL": "REL1_43",
3+
"Extensions": {
4+
"WMF": ["CookieWarning", "Math"],
5+
"Git": {
6+
"CrowdSec": {
7+
"type": "github",
8+
"repo": "ShinyColorsWiki/mediawiki-extension-crowdsec",
9+
"branch": "master"
10+
}
1811
},
19-
"Skins": {
20-
"WMF": [
21-
"MinervaNeue"
22-
],
23-
"Git": {
24-
"Timeless": {
25-
"type": "github",
26-
"repo": "wikimedia/mediawiki-skins-Timeless"
27-
}
28-
},
29-
"http": {
30-
"Vector": "https://github.com/wikimedia/Vector/archive/refs/heads/$mwrel.zip"
31-
}
12+
"http": {
13+
"AWS": "https://github.com/edwardspec/mediawiki-aws-s3/archive/refs/heads/master.zip"
3214
}
33-
}
15+
},
16+
"Skins": {
17+
"WMF": ["MinervaNeue"],
18+
"Git": {
19+
"Timeless": {
20+
"type": "github",
21+
"repo": "wikimedia/mediawiki-skins-Timeless"
22+
}
23+
},
24+
"http": {
25+
"Vector": "https://github.com/wikimedia/Vector/archive/refs/heads/$mwrel.zip"
26+
}
27+
}
28+
}

go.mod

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
module github.com/shinycolorswiki/mediawiki-extension-downloader
22

3-
go 1.18
3+
go 1.23
44

55
require (
6-
github.com/kataras/golog v0.1.9
6+
github.com/kataras/golog v0.1.12
77
github.com/mholt/archiver/v3 v3.5.1
8+
github.com/nwaples/rardecode v1.1.3
89
)
910

1011
require (
11-
github.com/andybalholm/brotli v1.0.1 // indirect
12+
github.com/andybalholm/brotli v1.1.1 // indirect
1213
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
13-
github.com/golang/snappy v0.0.2 // indirect
14-
github.com/kataras/pio v0.0.12 // indirect
15-
github.com/klauspost/compress v1.11.4 // indirect
16-
github.com/klauspost/pgzip v1.2.5 // indirect
17-
github.com/nwaples/rardecode v1.1.0 // indirect
18-
github.com/pierrec/lz4/v4 v4.1.2 // indirect
19-
github.com/ulikunitz/xz v0.5.9 // indirect
14+
github.com/golang/snappy v0.0.4 // indirect
15+
github.com/kataras/pio v0.0.13 // indirect
16+
github.com/klauspost/compress v1.17.11 // indirect
17+
github.com/klauspost/pgzip v1.2.6 // indirect
18+
github.com/pierrec/lz4/v4 v4.1.21 // indirect
19+
github.com/ulikunitz/xz v0.5.12 // indirect
2020
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
21-
golang.org/x/sys v0.9.0 // indirect
21+
golang.org/x/sys v0.27.0 // indirect
2222
)

go.sum

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,41 @@
1-
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
21
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
2+
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
3+
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
34
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
45
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
56
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
6-
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
77
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
8+
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
9+
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
810
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
9-
github.com/kataras/golog v0.1.9 h1:vLvSDpP7kihFGKFAvBSofYo7qZNULYSHOH2D7rPTKJk=
10-
github.com/kataras/golog v0.1.9/go.mod h1:jlpk/bOaYCyqDqH18pgDHdaJab72yBE6i0O3s30hpWY=
11-
github.com/kataras/pio v0.0.12 h1:o52SfVYauS3J5X08fNjlGS5arXHjW/ItLkyLcKjoH6w=
12-
github.com/kataras/pio v0.0.12/go.mod h1:ODK/8XBhhQ5WqrAhKy+9lTPS7sBf6O3KcLhc9klfRcY=
11+
github.com/kataras/golog v0.1.12 h1:Bu7I/G4ilJlbfzjmU39O9N+2uO1pBcMK045fzZ4ytNg=
12+
github.com/kataras/golog v0.1.12/go.mod h1:wrGSbOiBqbQSQznleVNX4epWM8rl9SJ/rmEacl0yqy4=
13+
github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM=
14+
github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM=
1315
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
14-
github.com/klauspost/compress v1.11.4 h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU=
1516
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
17+
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
18+
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
1619
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
17-
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
1820
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
21+
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
22+
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
1923
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
2024
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
21-
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
2225
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
23-
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
26+
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
27+
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
2428
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
29+
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
30+
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
2531
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
26-
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
2732
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
33+
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
34+
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
2835
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
2936
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
30-
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
31-
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
37+
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
38+
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
39+
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
40+
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
3241
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

main.go

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os"
88
"path/filepath"
99
"sync"
10+
"time"
1011

1112
"github.com/kataras/golog"
1213
)
@@ -33,6 +34,8 @@ func main() {
3334
config_path := flag.String("config", "./config.json", "A config file for download extensions and skins.")
3435
target_path := flag.String("target", "./downloaded", "A target folder for downloaded extensions and skins.")
3536
force_rm_target := flag.Bool("force-rm-target", false, "Turn this on to delete target directory if exist. Be careful to use!")
37+
retry_count := flag.Int("retry-count", 3, "Number of retries for download and extraction process.")
38+
retry_delay := flag.Int("retry-delay", 2, "Delay in seconds between retries for download and extraction process.")
3639
flag.Parse()
3740

3841
// Set flags to each variables.
@@ -92,7 +95,7 @@ func main() {
9295
wg.Add(len(DownloadTargets))
9396
for _, opts := range DownloadTargets {
9497
log.Debugf("Start download %s \"%s\"", opts.Type, opts.Name)
95-
go opts.StartDownload(&wg)
98+
go opts.StartDownload(&wg, *retry_count, time.Duration(*retry_delay)*time.Second)
9699
}
97100
wg.Wait()
98101

@@ -107,34 +110,60 @@ func main() {
107110
log.Info("Download Finished.")
108111
}
109112

110-
func (o DownloadOption) StartDownload(wg *sync.WaitGroup) {
113+
func (o DownloadOption) StartDownload(wg *sync.WaitGroup, retryCount int, retryDelay time.Duration) {
111114
defer wg.Done()
112115

113-
// remove "s" suffix
114-
target_name := o.Type[:len(o.Type)-1]
116+
err := retry(func() error {
117+
// remove "s" suffix
118+
log.Info(o.Type)
119+
targetName := o.Type[:len(o.Type)-1]
120+
121+
// Step 1: Download file from URL
122+
filename, err := downloadUrl(o.Name, o.Url)
123+
if err != nil {
124+
return fmt.Errorf("Failed to download %s \"%s\": %w", targetName, o.Name, err)
125+
}
126+
127+
// Step 2: Check if the file is a valid archive
128+
if !isValidArchiveFile(filename) {
129+
return fmt.Errorf("Downloaded file is not an archive: %s \"%s\"", targetName, o.Name)
130+
}
131+
132+
// Step 3: Extract the file to a temporary directory
133+
dirpath, err := unArchive(o.Name, filename)
134+
if err != nil {
135+
return fmt.Errorf("Failed to extract %s \"%s\": %w", targetName, o.Name, err)
136+
}
137+
138+
// Step 4: Move the extracted directory to the target location
139+
dest := fmt.Sprintf("%s/%s/%s", targetDir, o.Type, o.Name)
140+
err = os.Rename(dirpath, dest)
141+
if err != nil {
142+
return fmt.Errorf("Failed to move %s \"%s\" to \"%s\": %w", targetName, o.Name, dest, err)
143+
}
144+
145+
return nil
146+
}, retryCount, retryDelay, fmt.Sprintf("Complete download and extraction process for \"%s\"", o.Name))
115147

116-
// Download file from url.
117-
filename, err := downloadUrl(o.Name, o.Url)
118148
if err != nil {
119-
msg := fmt.Sprintf("Failed to download %s \"%s\" ", target_name, o.Name)
120-
log.Error(msg, err)
121-
hasError = true
122-
}
123-
124-
// Extract to temp directory.
125-
dirpath, err := unArchive(o.Name, filename)
126-
if err != nil {
127-
msg := fmt.Sprintf("Failed to extract %s \"%s\" to \"%s\" ", target_name, o.Name, dirpath)
128-
log.Error(msg, err)
149+
log.Error(err)
129150
hasError = true
130151
}
152+
}
131153

132-
// And move to target.
133-
dest := fmt.Sprintf("%s/%s/%s", targetDir, o.Type, o.Name)
134-
err = os.Rename(dirpath, dest)
135-
if err != nil {
136-
msg := fmt.Sprintf("Failed to move %s \"%s\" from \"%s\" to \"%s\"", target_name, o.Name, dirpath, dest)
137-
log.Error(msg, err)
138-
hasError = true
154+
// retry function to handle retries with delay for entire process
155+
func retry(operation func() error, attempts int, delay time.Duration, description string) error {
156+
for i := 0; i < attempts; i++ {
157+
err := operation()
158+
if err == nil {
159+
return nil
160+
}
161+
if i < attempts-1 {
162+
log.Warnf("Retrying %s... attempt %d", description, i+2)
163+
time.Sleep(delay)
164+
} else {
165+
return fmt.Errorf("failed to %s after %d attempts: %w", description, attempts, err)
166+
}
139167
}
168+
return nil
140169
}

main_test.go

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,96 @@
11
package main
22

33
import (
4-
"fmt"
5-
"math/rand"
64
"os"
5+
"path/filepath"
76
"testing"
87
)
98

109
func TestMainProgram(t *testing.T) {
11-
tempdir := fmt.Sprintf("/tmp/mediawiki-extension-downloader-test-%d", rand.Int())
10+
// Create a temporary directory for the test using t.TempDir()
11+
tempDir := t.TempDir()
1212

13-
os.Args = []string{"program",
13+
// Set up command-line arguments for the main program.
14+
os.Args = []string{
15+
"program",
1416
"--config", "./config.example.json",
15-
"--target", tempdir}
17+
"--target", tempDir,
18+
}
19+
20+
// Run the main program.
1621
main()
1722

18-
// cleanup tmp directory
19-
err := os.RemoveAll(tempdir)
23+
// Read and validate the configuration file.
24+
config, err := readConfig("./config.example.json")
2025
if err != nil {
21-
log.Child("TEST").Error("Unknown error occured during cleanup. This doesn't affect the test. ", err)
26+
t.Fatalf("Error reading config: %v", err)
27+
}
28+
29+
// Calculate the total number of extensions and skins.
30+
extensionsLen := len(config.Extensions.WMF) + len(config.Extensions.Git) + len(config.Extensions.Http)
31+
if extensionsLen == 0 {
32+
t.Fatal("No extensions found in config file.")
33+
}
34+
35+
skinsLen := len(config.Skins.WMF) + len(config.Skins.Git) + len(config.Skins.Http)
36+
if skinsLen == 0 {
37+
t.Fatal("No skins found in config file.")
38+
}
39+
40+
t.Log("Config file read successfully.")
41+
42+
// Verify the extensions directory.
43+
extensionsDir := filepath.Join(tempDir, "extensions")
44+
extEntries, err := os.ReadDir(extensionsDir)
45+
if err != nil {
46+
t.Fatalf("Error reading extensions directory: %v", err)
47+
}
48+
t.Logf("Extensions found: %v", extEntries)
49+
t.Logf("Expected number of extensions: %d", extensionsLen)
50+
51+
if len(extEntries) == 0 || len(extEntries) != extensionsLen {
52+
t.Fatalf("Extensions directory is empty or does not match config. Expected: %d, Found: %d", extensionsLen, len(extEntries))
53+
}
54+
55+
// Ensure each extension directory contains at least two files.
56+
for _, entry := range extEntries {
57+
extPath := filepath.Join(extensionsDir, entry.Name())
58+
files, err := os.ReadDir(extPath)
59+
if err != nil {
60+
t.Fatalf("Error reading extension directory '%s': %v", extPath, err)
61+
}
62+
if len(files) < 2 {
63+
t.Fatalf("Extension directory '%s' should have at least 2 files. Found: %d", extPath, len(files))
64+
}
65+
}
66+
67+
// Verify the skins directory.
68+
skinsDir := filepath.Join(tempDir, "skins")
69+
skinEntries, err := os.ReadDir(skinsDir)
70+
if err != nil {
71+
t.Fatalf("Error reading skins directory: %v", err)
72+
}
73+
t.Logf("Skins found: %v", skinEntries)
74+
t.Logf("Expected number of skins: %d", skinsLen)
75+
76+
if len(skinEntries) == 0 || len(skinEntries) != skinsLen {
77+
t.Fatalf("Skins directory is empty or does not match config. Expected: %d, Found: %d", skinsLen, len(skinEntries))
78+
}
79+
80+
// Ensure each skin directory contains at least two files.
81+
for _, entry := range skinEntries {
82+
skinPath := filepath.Join(skinsDir, entry.Name())
83+
files, err := os.ReadDir(skinPath)
84+
if err != nil {
85+
t.Fatalf("Error reading skin directory '%s': %v", skinPath, err)
86+
}
87+
if len(files) < 2 {
88+
t.Fatalf("Skin directory '%s' should have at least 2 files. Found: %d", skinPath, len(files))
89+
}
2290
}
2391

24-
if hasError {
25-
log.Child("TEST").Fatal("Error during test")
92+
// Final check to ensure the temporary directory exists.
93+
if _, err := os.Stat(tempDir); os.IsNotExist(err) {
94+
t.Fatalf("Temporary directory not found: %s", tempDir)
2695
}
2796
}

0 commit comments

Comments
 (0)