Skip to content

Commit 2b84fc4

Browse files
authored
Merge pull request #559 from pfitaxel/fix-open-close
fix(teacher_tab): partly fix Open/Close handling w.r.t. Assignments Close #534 Related: #558
2 parents b4d68e8 + 10c9fc3 commit 2b84fc4

12 files changed

+368
-47
lines changed

.ci-macosx.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,13 @@ opam init -y -a --bare
3535

3636
opam switch create . ocaml-base-compiler --deps-only --locked -y -j 2 # -v
3737
eval $(opam env)
38+
39+
# Run unit tests
40+
# Note: we might want to run them in Linux as well in the CI
41+
make test
42+
3843
make
44+
3945
make opaminstall
4046

4147
# See src/main/linking_flags.sh

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ build-deps:
1111
build:
1212
@${DUNE} build ${DUNE_ARGS}
1313

14+
.PHONY: test
15+
test:
16+
@${DUNE} runtest --root .
17+
18+
.PHONY: test-promote
19+
test-promote:
20+
@${DUNE} runtest --root . --auto-promote
21+
1422
.PHONY: static
1523
static:
1624
@${MAKE} -C static

learn-ocaml-client.opam

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ depends: [
3939
"ocplib-json-typed" {>= "0.7"}
4040
"ocp-ocamlres" {>= "0.4"}
4141
"omd" {<= "1.3.1"}
42-
"ppx_fields_conv"
4342
"ppxlib"
43+
"ppx_expect"
44+
"ppx_inline_test"
45+
"ppx_fields_conv"
4446
"ppx_sexp_conv"
4547
"ppx_tools"
4648
"ssl" {= "0.5.12"}

learn-ocaml-client.opam.locked

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ depends: [
4242
"conduit-lwt-unix" {= "1.3.0"}
4343
"conf-libssl" {= "3"}
4444
"conf-pkg-config" {= "2"}
45-
"conf-which" {= "1"}
4645
"cppo" {= "1.6.7"}
4746
"csexp" {= "1.5.1"}
4847
"cstruct" {= "5.0.0"}
@@ -57,11 +56,12 @@ depends: [
5756
"gg" {= "0.9.3"}
5857
"hex" {= "1.4.0"}
5958
"ipaddr" {= "2.9.0"}
60-
"jbuilder" {= "1.0+beta20.2"}
59+
"jane-street-headers" {= "v0.14.0"}
6160
"js_of_ocaml" {= "3.9.0"}
6261
"js_of_ocaml-compiler" {= "3.9.1"}
6362
"js_of_ocaml-ppx" {= "3.9.0"}
6463
"jsonm" {= "1.0.1"}
64+
"jst-config" {= "v0.14.1"}
6565
"logs" {= "0.7.0"}
6666
"lwt" {= "5.4.1"}
6767
"lwt_ssl" {= "1.1.3"}
@@ -83,11 +83,23 @@ depends: [
8383
"ocp-ocamlres" {= "0.4"}
8484
"ocplib-endian" {= "1.1"}
8585
"ocplib-json-typed" {= "0.7.1"}
86+
"octavius" {= "1.2.2"}
8687
"omd" {= "1.3.1"}
8788
"parsexp" {= "v0.14.1"}
8889
"pprint" {= "20200410"}
90+
"ppx_assert" {= "v0.14.0"}
91+
"ppx_base" {= "v0.14.0"}
92+
"ppx_cold" {= "v0.14.0"}
93+
"ppx_compare" {= "v0.14.0"}
8994
"ppx_derivers" {= "1.2.1"}
95+
"ppx_enumerate" {= "v0.14.0"}
96+
"ppx_expect" {= "v0.14.0"}
9097
"ppx_fields_conv" {= "v0.14.1"}
98+
"ppx_hash" {= "v0.14.0"}
99+
"ppx_here" {= "v0.14.0"}
100+
"ppx_inline_test" {= "v0.14.1"}
101+
"ppx_js_style" {= "v0.14.1"}
102+
"ppx_optcomp" {= "v0.14.0"}
91103
"ppx_sexp_conv" {= "v0.14.1"}
92104
"ppx_tools" {= "6.3"}
93105
"ppxlib" {= "0.15.0"}
@@ -97,8 +109,10 @@ depends: [
97109
"sexplib" {= "v0.14.0"}
98110
"sexplib0" {= "v0.14.0"}
99111
"ssl" {= "0.5.12"}
112+
"stdio" {= "v0.14.0"}
100113
"stdlib-shims" {= "0.3.0"}
101114
"stringext" {= "1.6.0"}
115+
"time_now" {= "v0.14.0"}
102116
"topkg" {= "1.0.3"}
103117
"uchar" {= "0.0.2"}
104118
"uri" {= "4.2.0"}

learn-ocaml.opam

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@ depends: [
5454
"odoc" {build}
5555
"omd" {<= "1.3.1"}
5656
"pprint"
57-
"ppx_cstruct"
5857
"ppxlib"
58+
"ppx_cstruct"
59+
"ppx_expect"
60+
"ppx_inline_test"
5961
"ppx_sexp_conv"
6062
"ppx_tools"
6163
"ppx_tools_versioned"
@@ -70,6 +72,7 @@ build: [
7072
["dune" "build" "-p" name "-j" jobs]
7173
[make "detect-libs"] {with-test}
7274
]
75+
run-test: [make "test"]
7376
install: [
7477
["mkdir" "-p" "%{_:share}%"]
7578
["cp" "-r" "demo-repository" "%{_:share}%/repository"]

learn-ocaml.opam.locked

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,15 @@ depends: [
5757
"gg" {= "0.9.3"}
5858
"hex" {= "1.4.0"}
5959
"ipaddr" {= "2.9.0"}
60-
"jbuilder" {= "1.0+beta20.2"}
60+
"jane-street-headers" {= "v0.14.0"}
6161
"js_of_ocaml" {= "3.9.0"}
6262
"js_of_ocaml-compiler" {= "3.9.1"}
6363
"js_of_ocaml-lwt" {= "3.9.0"}
6464
"js_of_ocaml-ppx" {= "3.9.0"}
6565
"js_of_ocaml-toplevel" {= "3.9.0"}
6666
"js_of_ocaml-tyxml" {= "3.9.0"}
6767
"jsonm" {= "1.0.1"}
68+
"jst-config" {= "v0.14.1"}
6869
"logs" {= "0.7.0"}
6970
"lwt" {= "5.4.1"}
7071
"lwt_react" {= "1.1.4"}
@@ -90,13 +91,25 @@ depends: [
9091
"ocplib-endian" {= "1.1"}
9192
"ocplib-json-typed" {= "0.7.1"}
9293
"ocplib-json-typed-browser" {= "0.7.1"}
94+
"octavius" {= "1.2.2"}
9395
"odoc" {= "1.5.3"}
9496
"omd" {= "1.3.1"}
9597
"optint" {= "0.1.0"}
9698
"parsexp" {= "v0.14.1"}
9799
"pprint" {= "20200410"}
100+
"ppx_assert" {= "v0.14.0"}
101+
"ppx_base" {= "v0.14.0"}
102+
"ppx_cold" {= "v0.14.0"}
103+
"ppx_compare" {= "v0.14.0"}
98104
"ppx_cstruct" {= "5.0.0"}
99105
"ppx_derivers" {= "1.2.1"}
106+
"ppx_enumerate" {= "v0.14.0"}
107+
"ppx_expect" {= "v0.14.0"}
108+
"ppx_hash" {= "v0.14.0"}
109+
"ppx_here" {= "v0.14.0"}
110+
"ppx_inline_test" {= "v0.14.1"}
111+
"ppx_js_style" {= "v0.14.1"}
112+
"ppx_optcomp" {= "v0.14.0"}
100113
"ppx_sexp_conv" {= "v0.14.1"}
101114
"ppx_tools" {= "6.3"}
102115
"ppx_tools_versioned" {= "5.4.0"}
@@ -109,8 +122,10 @@ depends: [
109122
"sexplib" {= "v0.14.0"}
110123
"sexplib0" {= "v0.14.0"}
111124
"ssl" {= "0.5.12"}
125+
"stdio" {= "v0.14.0"}
112126
"stdlib-shims" {= "0.3.0"}
113127
"stringext" {= "1.6.0"}
128+
"time_now" {= "v0.14.0"}
114129
"topkg" {= "1.0.3"}
115130
"tyxml" {= "4.4.0"}
116131
"uchar" {= "0.0.2"}
@@ -123,6 +138,7 @@ depends: [
123138
build: [
124139
[make "static"]
125140
["dune" "build" "-p" name "-j" jobs]
141+
[make "detect-libs"] {with-test}
126142
]
127143
install: [
128144
["mkdir" "-p" "%{_:share}%"]
@@ -133,3 +149,4 @@ description: """
133149
This contains the binaries forming the engine for the learn-ocaml platform, and
134150
the common files. A demo exercise repository is also provided as example.
135151
"""
152+
run-test: [make "test"]

src/app/learnocaml_teacher_tab.ml

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -299,13 +299,13 @@ let rec teacher_tab token _select _params () =
299299
H.td [stars_div meta.Exercise.Meta.stars];
300300
H.td [
301301
let cls, text =
302-
match Token.Map.is_empty ES.(st.assignments.token_map),
303-
ES.(st.assignments.default) with
304-
| true, ES.Open -> "exo_open", [%i"Open"]
305-
| true, ES.Closed -> "exo_closed", [%i"Closed"]
306-
| _, (ES.Assigned _ | ES.Closed) ->
307-
"exo_assigned", [%i"Assigned"]
308-
| false, ES.Open -> "exo_assigned", [%i"Open/Assg"]
302+
let open ES in
303+
match is_open_or_assigned_globally st.assignments with
304+
| GloballyOpen -> "exo_open", [%i"Open"]
305+
| GloballyOpenOrAssigned -> "exo_assigned", [%i"Open/Assigned"]
306+
| GloballyClosedOrAssigned -> "exo_assigned", [%i"Assigned"]
307+
| GloballyClosed -> "exo_closed", [%i"Closed"]
308+
| GloballyInconsistent -> "exo_closed", [%i"Inconsistent"]
309309
in
310310
H.span ~a:[H.a_class [cls]] [H.txt text]
311311
];
@@ -856,17 +856,13 @@ let rec teacher_tab token _select _params () =
856856
let ids = htbl_keys selected_exercises in
857857
let fstat =
858858
if List.exists (fun id ->
859-
let st = get_status id in
860-
ES.(default_assignment st.assignments = Open))
859+
let st = get_status id in
860+
let open_assg = ES.is_open_or_assigned_globally st.ES.assignments in
861+
open_assg = ES.GloballyOpen || open_assg = ES.GloballyOpenOrAssigned
862+
|| open_assg = ES.GloballyInconsistent)
861863
ids
862-
then ES.(fun assg ->
863-
match default_assignment assg with
864-
| Open -> set_default_assignment assg Closed
865-
| _ -> assg)
866-
else ES.(fun assg ->
867-
match default_assignment assg with
868-
| Closed -> set_default_assignment assg Open
869-
| _ -> assg)
864+
then ES.set_close_or_assigned_globally
865+
else ES.set_open_or_assigned_globally
870866
in
871867
!exercise_status_change (htbl_keys selected_exercises) fstat;
872868
true)
@@ -1330,7 +1326,11 @@ let rec teacher_tab token _select _params () =
13301326
in
13311327
let open_exercises =
13321328
SMap.fold (fun ex st acc ->
1333-
if ES.(st.assignments.default = Open) then ex::acc else acc)
1329+
let open ES in
1330+
let global_st = is_open_or_assigned_globally st.assignments in
1331+
if global_st = GloballyOpen
1332+
|| global_st = GloballyOpenOrAssigned
1333+
then ex :: acc else acc)
13341334
!status_map []
13351335
|> List.rev
13361336
in

src/state/dune

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@
1515
learnocaml_repository)
1616
)
1717

18+
(library
19+
(name learnocaml_data_test)
20+
(wrapped false)
21+
(modules Learnocaml_data_test)
22+
(libraries learnocaml_data)
23+
(inline_tests)
24+
(preprocess (pps ppx_expect ppx_inline_test))
25+
)
26+
1827
(library
1928
(name learnocaml_api)
2029
(wrapped false)

src/state/learnocaml_data.ml

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -508,11 +508,126 @@ module Exercise = struct
508508

509509
let set_default_assignment a default = {a with default}
510510

511+
let make_assignments token_map default =
512+
{ token_map; default }
513+
511514
let get_status token a =
512515
match Token.Map.find_opt token a.token_map with
513516
| Some a -> a
514517
| None -> a.default
515518

519+
(* Global assignment status, w.r.t. all students as a whole
520+
521+
Invariants: forall exo_status : t,
522+
523+
1.(REQUIRED):
524+
(exo_status.assignments.default <> Open && Token.Map.for_all (fun _ st -> st <> Open) exo_status.assignments.token_map)
525+
|| (exo_status.assignments.default <> Closed && Token.Map.for_all (fun _ st -> st <> Closed) exo_status.assignments.token_map)
526+
527+
2.(IfNormalized):
528+
is_open_assigned_globally exo_status.assignments \in \{GloballyOpen, GloballyClosed\} ->
529+
exo_status.assignments.token_map = Token.Map.empty *)
530+
type global_status =
531+
| GloballyOpen (** "Open" *)
532+
| GloballyClosed (** "Closed" *)
533+
| GloballyOpenOrAssigned (** "Open/Assigned" *)
534+
| GloballyClosedOrAssigned (** "Assigned" *)
535+
| GloballyInconsistent (** "Inconsistent" *)
536+
537+
let check_open_close a =
538+
match a.default with
539+
| Open ->
540+
Token.Map.for_all (fun _tok st -> st <> Closed) a.token_map
541+
| Closed ->
542+
Token.Map.for_all (fun _tok st -> st <> Open) a.token_map
543+
| Assigned _ ->
544+
let o, c =
545+
Token.Map.fold (fun _tok st (o, c) ->
546+
(o && st <> Closed,
547+
c && st <> Open)) a.token_map (true, true) in
548+
o || c
549+
550+
let fix_open_close ?(close=true) a =
551+
if close then
552+
let mp =
553+
Token.Map.map (function Open -> Closed | st -> st) a.token_map in
554+
match a.default with
555+
| Open | Closed ->
556+
make_assignments mp Closed
557+
| assg ->
558+
make_assignments mp assg
559+
else
560+
let mp =
561+
Token.Map.map (function Closed -> Open | st -> st) a.token_map in
562+
match a.default with
563+
| Open | Closed ->
564+
make_assignments mp Open
565+
| assg ->
566+
make_assignments mp assg
567+
568+
let check_and_fix_open_close a =
569+
if check_open_close a then a
570+
else fix_open_close a
571+
572+
let is_open_or_assigned_globally a =
573+
match a.default with
574+
| Assigned _ ->
575+
let o, c =
576+
Token.Map.fold (fun _tok st (o, c) ->
577+
(o || st = Open,
578+
c || st = Closed)) a.token_map (false, false) in
579+
begin match o, c with
580+
| true, true -> GloballyInconsistent
581+
| true, false -> GloballyOpenOrAssigned
582+
| false, _ -> GloballyClosedOrAssigned
583+
end
584+
| Open ->
585+
let d, c =
586+
Token.Map.fold (fun _tok st (d, c) ->
587+
(d || (match st with Assigned _ -> true | _ -> false),
588+
c || st = Closed)) a.token_map (false, false) in
589+
begin match d, c with
590+
| _, true -> GloballyInconsistent
591+
| true, false -> GloballyOpenOrAssigned
592+
| false, false -> GloballyOpen
593+
end
594+
| Closed ->
595+
let d, o =
596+
Token.Map.fold (fun _tok st (d, o) ->
597+
(d || (match st with Assigned _ -> true | _ -> false),
598+
o || st = Open)) a.token_map (false, false) in
599+
begin match d, o with
600+
| _, true -> GloballyInconsistent
601+
| true, false -> GloballyClosedOrAssigned
602+
| false, false -> GloballyClosed
603+
end
604+
605+
let set_close_or_assigned_globally a =
606+
match is_open_or_assigned_globally a with
607+
| GloballyOpen -> make_assignments Token.Map.empty Closed
608+
| GloballyOpenOrAssigned ->
609+
make_assignments
610+
(Token.Map.map (function Open -> Closed | st -> st) a.token_map)
611+
(match a.default with Open -> Closed | a -> a)
612+
(* otherwise, maybe: forget the map and re-add all tokens ? *)
613+
| GloballyClosedOrAssigned -> a
614+
| GloballyClosed -> a
615+
| GloballyInconsistent -> fix_open_close ~close:true a
616+
617+
let set_open_or_assigned_globally a =
618+
match is_open_or_assigned_globally a with
619+
| GloballyClosed -> make_assignments Token.Map.empty Open
620+
| GloballyClosedOrAssigned ->
621+
make_assignments
622+
(Token.Map.map (function Closed -> Open | st -> st) a.token_map)
623+
(match a.default with Closed -> Open | a -> a)
624+
(* otherwise, maybe: forget the map and re-add all tokens ? *)
625+
| GloballyOpenOrAssigned -> a
626+
| GloballyOpen -> a
627+
| GloballyInconsistent -> fix_open_close ~close:false a
628+
629+
(* Note/Erik: we may also want to implement set_assigned_globally *)
630+
516631
let is_open_assignment token a =
517632
match get_status token a with
518633
| Assigned a ->
@@ -640,9 +755,6 @@ module Exercise = struct
640755
skills_focus;
641756
assignments = { default; token_map } }
642757

643-
let make_assignments token_map default =
644-
{ token_map; default }
645-
646758
let enc =
647759
let status_enc =
648760
J.union [

0 commit comments

Comments
 (0)