github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/assignments/walk_tests_repo.go (about) 1 package assignments 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/fs" 7 "os" 8 "path/filepath" 9 "sort" 10 11 "github.com/quickfeed/quickfeed/qf" 12 ) 13 14 const ( 15 assignmentFile = "assignment.yml" 16 assignmentFileYaml = "assignment.yaml" 17 criteriaFile = "criteria.json" 18 dockerfile = "Dockerfile" 19 taskFilePattern = "task-*.md" 20 ) 21 22 var patterns = []string{ 23 assignmentFile, 24 assignmentFileYaml, 25 criteriaFile, 26 dockerfile, 27 taskFilePattern, 28 } 29 30 // matchAny returns true if filename matches one of the target patterns. 31 func matchAny(filename string) bool { 32 for _, pattern := range patterns { 33 if ok, _ := filepath.Match(pattern, filename); ok { 34 return true 35 } 36 } 37 return false 38 } 39 40 // match returns true if filename matches the given pattern. 41 func match(filename, pattern string) bool { 42 if ok, _ := filepath.Match(pattern, filename); ok { 43 return true 44 } 45 return false 46 } 47 48 // readTestsRepositoryContent reads dir and returns a list of assignments and 49 // the course's Dockerfile content if there exists a 'tests/scripts/Dockerfile'. 50 // Assignments are extracted from 'assignment.yml' files, one for each assignment. 51 func readTestsRepositoryContent(dir string, courseID uint64) ([]*qf.Assignment, string, error) { 52 files, err := walkTestsRepository(dir) 53 if err != nil { 54 return nil, "", err 55 } 56 57 // Process all assignment.yml files first 58 assignmentsMap := make(map[string]*qf.Assignment) 59 for path, contents := range files { 60 assignmentName := filepath.Base(filepath.Dir(path)) 61 switch filepath.Base(path) { 62 case assignmentFile, assignmentFileYaml: 63 assignment, err := newAssignmentFromFile(contents, assignmentName, courseID) 64 if err != nil { 65 return nil, "", err 66 } 67 assignmentsMap[assignmentName] = assignment 68 } 69 } 70 71 var courseDockerfile string 72 73 // Process other files in tests repository 74 for path, contents := range files { 75 assignmentName := filepath.Base(filepath.Dir(path)) 76 filename := filepath.Base(path) 77 78 switch filename { 79 case criteriaFile: 80 var benchmarks []*qf.GradingBenchmark 81 if err := json.Unmarshal(contents, &benchmarks); err != nil { 82 return nil, "", fmt.Errorf("failed to unmarshal %q: %s", criteriaFile, err) 83 } 84 // Benchmarks and criteria must have courseID 85 // for access control checks. 86 for _, bm := range benchmarks { 87 bm.CourseID = courseID 88 for _, c := range bm.Criteria { 89 c.CourseID = courseID 90 } 91 } 92 assignmentsMap[assignmentName].GradingBenchmarks = benchmarks 93 94 case dockerfile: 95 courseDockerfile = string(contents) 96 } 97 98 if match(filename, taskFilePattern) { 99 assignment := assignmentsMap[assignmentName] 100 taskName := taskName(filename) 101 task, err := newTask(contents, assignment.GetOrder(), taskName) 102 if err != nil { 103 return nil, "", err 104 } 105 assignmentsMap[assignmentName].Tasks = append(assignmentsMap[assignmentName].Tasks, task) 106 } 107 } 108 109 assignments := make([]*qf.Assignment, 0) 110 for _, assignment := range assignmentsMap { 111 assignments = append(assignments, assignment) 112 sort.Slice(assignment.Tasks, func(i, j int) bool { 113 return assignment.Tasks[i].Title < assignment.Tasks[j].Title 114 }) 115 } 116 sort.Slice(assignments, func(i, j int) bool { 117 return assignments[i].Order < assignments[j].Order 118 }) 119 120 return assignments, courseDockerfile, nil 121 } 122 123 // walkTestsRepository walks the tests repository and returns a map of file names and their contents. 124 func walkTestsRepository(dir string) (map[string][]byte, error) { 125 if _, err := os.Stat(dir); os.IsNotExist(err) { 126 return nil, err 127 } 128 files := make(map[string][]byte) 129 err := filepath.WalkDir(dir, func(path string, info fs.DirEntry, err error) error { 130 if err != nil { 131 // Walk unable to read path; stop walking the tree 132 return err 133 } 134 if !info.IsDir() && matchAny(info.Name()) { 135 if files[path], err = os.ReadFile(path); err != nil { 136 return err 137 } 138 } 139 return nil 140 }) 141 if err != nil { 142 return nil, err 143 } 144 return files, nil 145 }