mumukit 2.4.0 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +5 -5
  2. data/lib/locales/en.yml +11 -0
  3. data/lib/locales/es.yml +11 -0
  4. data/lib/mumukit/compilation_error.rb +4 -0
  5. data/lib/mumukit/cookie.rb +19 -0
  6. data/lib/mumukit/defaults/default_try_hook.rb +9 -0
  7. data/lib/mumukit/defaults/default_version_hook.rb +3 -0
  8. data/lib/mumukit/defaults.rb +2 -0
  9. data/lib/mumukit/explainer.rb +23 -0
  10. data/lib/mumukit/hook.rb +7 -0
  11. data/lib/mumukit/isolated_environment.rb +24 -12
  12. data/lib/mumukit/metatest/checker.rb +6 -6
  13. data/lib/mumukit/metatest/interactive_checker.rb +59 -0
  14. data/lib/mumukit/metatest.rb +1 -0
  15. data/lib/mumukit/runner.rb +18 -0
  16. data/lib/mumukit/runtime/info.rb +2 -1
  17. data/lib/mumukit/runtime/runtime.rb +4 -2
  18. data/lib/mumukit/runtime/symbol.rb +2 -2
  19. data/lib/mumukit/server/app.rb +4 -0
  20. data/lib/mumukit/server/response_builder.rb +4 -0
  21. data/lib/mumukit/server/test_pipeline.rb +30 -0
  22. data/lib/mumukit/server/test_server.rb +27 -36
  23. data/lib/mumukit/server.rb +1 -0
  24. data/lib/mumukit/templates/file_hook.rb +33 -3
  25. data/lib/mumukit/templates/mulang_expectations_hook.rb +62 -20
  26. data/lib/mumukit/templates/try_hook.rb +17 -0
  27. data/lib/mumukit/templates/with_cookie.rb +10 -0
  28. data/lib/mumukit/templates/with_line_number_offset.rb +19 -0
  29. data/lib/mumukit/templates/with_mashup_file_content.rb +5 -1
  30. data/lib/mumukit/templates/with_metatest.rb +14 -0
  31. data/lib/mumukit/templates/with_metatest_results.rb +31 -0
  32. data/lib/mumukit/templates/with_structured_results.rb +13 -2
  33. data/lib/mumukit/templates.rb +5 -0
  34. data/lib/mumukit/version.rb +1 -1
  35. data/lib/mumukit/with_tempfile.rb +3 -0
  36. data/lib/mumukit.rb +6 -10
  37. metadata +76 -11
  38. data/bin/mulang +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 3f8afb7358f144c2e25c356c70f0061144ff2f04
4
- data.tar.gz: d41e834b6c92fea4897c87d46f5d3303983a8209
2
+ SHA256:
3
+ metadata.gz: b678748b9bbd865a42951a22eb71244968362faf551452fdfe4d0a9f14889fda
4
+ data.tar.gz: 42ea663c41b564d65222f11d7baa5b7447a2e49b053eb8579b4ec2d8ad814268
5
5
  SHA512:
6
- metadata.gz: b4830f051f2f4ede2c1a76f05306974ea4c8d05947b2a0b8e4d2f7c3b3ad5dbb32207045accb30c10815bd90dd2e6ccc8a9e982a947e48c0fd17df2582b67e63
7
- data.tar.gz: d1c908995f05aeab6cd7bc6a47c4614a20ec242cb3e49fee4497060c03ebc87a3636578652a3fb3716016543861405db1c7a4cd94335772303ed5cc30a06d30a
6
+ metadata.gz: bbb8f3d2905dc3b02b600efb96609bbfb888410228822d40ad55977e59aee51683d1842afb8faa5816b3c76a846639bd4de364f707bc0600d4817c8184b6f785
7
+ data.tar.gz: 0cfadb4c1dc8c55e3ed8b903c0d3d05c2217f56d39845bca17fcc97ac6888b8d5bfa7ca2c48f87f714849ed52a86302eb2893d3168b64c553d915bb975331ba5
data/lib/locales/en.yml CHANGED
@@ -2,3 +2,14 @@ en:
2
2
  mumukit:
3
3
  memory_exceeded: Memory limit exceeded. Is your program trying to allocate to much memory?
4
4
  time_exceeded: Execution time limit of %{limit}s exceeded. Is your program performing an infinite loop or recursion?
5
+ masked_tempfile_basename: solution
6
+ interactive:
7
+ check_last_query_equals: "query should be '%{expected}' but was '%{actual}'"
8
+ check_last_query_fails: "query should fail but it passed"
9
+ check_last_query_matches: "query does not match regex %{regex}"
10
+ check_last_query_outputs: "query output should be '%{expected}' but was '%{actual}'"
11
+ check_last_query_passes: "query should pass but it failed"
12
+ check_query_fails: "query '%{query}' should fail but it passed"
13
+ check_query_outputs: "query '%{query}' output should be '%{expected}' but was '%{actual}'"
14
+ check_query_passes: "query '%{query}' should pass but it failed"
15
+ goal_passed: "goal was met successfully"
data/lib/locales/es.yml CHANGED
@@ -2,3 +2,14 @@ es:
2
2
  mumukit:
3
3
  memory_exceeded: Limite de memoria excedido. ¿Tu programa está alocando demasiada memoria?
4
4
  time_exceeded: Limite de tiempo de ejecución de %{limit}s excedido. ¿Tu programa tiene recursión o bucle infinito?
5
+ masked_tempfile_basename: solucion
6
+ interactive:
7
+ check_last_query_equals: "La consulta debería ser '%{expected}' pero fue '%{actual}'"
8
+ check_last_query_fails: "La consulta debería fallar pero pasó"
9
+ check_last_query_matches: "La consulta no cumple con la expresión regular %{regex}"
10
+ check_last_query_outputs: "La consulta debería devolver '%{expected}' pero devolvió '%{actual}'"
11
+ check_last_query_passes: "La consulta debería pasar pero falló"
12
+ check_query_fails: "La consulta '%{query}' debería fallar pero pasó"
13
+ check_query_outputs: "La consulta '%{query}' debería devolver '%{expected}' pero devolvió '%{actual}'"
14
+ check_query_passes: "La consulta '%{query}' debería pasar pero falló"
15
+ goal_passed: "El objetivo se cumplió exitosamente"
@@ -0,0 +1,4 @@
1
+ module Mumukit
2
+ class CompilationError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,19 @@
1
+ class Mumukit::Cookie
2
+ attr_accessor :statements
3
+
4
+ def initialize(statements)
5
+ @statements = statements || []
6
+ end
7
+
8
+ def code
9
+ "#{statements_code}\n#{stdout_separator_code}\n"
10
+ end
11
+
12
+ def trim(output)
13
+ output.split("#{stdout_separator}\n")[1]
14
+ end
15
+
16
+ def stdout_separator
17
+ '!!!!MUMUKI-COOKIE-STDOUT-SEPARATOR-END!!!!'
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ class Mumukit::Defaults::TryHook < Mumukit::Hook
2
+ def compile(request)
3
+ request
4
+ end
5
+
6
+ def run!(compilation)
7
+ ['unimplemented', :aborted, {result: 'unimplemented', status: :aborted}]
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ class Mumukit::Defaults::VersionHook
2
+ VERSION = File.read('version') rescue 'master'
3
+ end
@@ -3,9 +3,11 @@ module Mumukit
3
3
  end
4
4
  end
5
5
 
6
+ require_relative './defaults/default_version_hook'
6
7
  require_relative './defaults/default_expectations_hook'
7
8
  require_relative './defaults/default_query_hook'
8
9
  require_relative './defaults/default_feedback_hook'
9
10
  require_relative './defaults/default_validation_hook'
10
11
  require_relative './defaults/default_test_hook'
12
+ require_relative './defaults/default_try_hook'
11
13
  require_relative './defaults/default_metadata_hook'
@@ -0,0 +1,23 @@
1
+ class Mumukit::Explainer
2
+ def explain(content, test_results)
3
+ explain_methods
4
+ .map { |selector, key| eval_explain(selector, key, content, test_results) }
5
+ .compact
6
+ .map { |explain| I18n.t(explain[:key], explain[:binding]) }
7
+ .map { |it| "* #{it}" }
8
+ .join("\n")
9
+ end
10
+
11
+ def eval_explain(selector, key, content, test_results)
12
+ send(selector, content, test_results).try do |it|
13
+ {key: key, binding: it}
14
+ end
15
+ end
16
+
17
+ def explain_methods
18
+ self.class
19
+ .instance_methods(false)
20
+ .flat_map { |it| it.to_s.captures(/explain_(.*)/).map { [it, $1] } }
21
+ .compact
22
+ end
23
+ end
data/lib/mumukit/hook.rb CHANGED
@@ -27,4 +27,11 @@ class Mumukit::Hook
27
27
  def should_forward_to_config?(args, name)
28
28
  args.length == 0 && !block_given? && @config[name]
29
29
  end
30
+
31
+ def self.stateful_through(cookie_clazz)
32
+ include Mumukit::Templates::WithCookie
33
+ define_method :cookie_class do
34
+ cookie_clazz
35
+ end
36
+ end
30
37
  end
@@ -16,21 +16,22 @@ module Mumukit
16
16
 
17
17
  command = yield(*filenames).split
18
18
 
19
+ configure_container! command, binds, volumes
20
+ end
21
+
22
+ def configure_container!(command, binds, volumes)
19
23
  self.container = Docker::Container.create(
20
- 'Image' => Mumukit.config.docker_image,
21
- 'Cmd' => command,
22
- 'NetworkDisabled' => true,
23
- 'HostConfig' => {
24
- 'Binds' => binds},
25
- 'Volumes' => volumes)
24
+ 'Image' => Mumukit.config.docker_image,
25
+ 'Cmd' => command,
26
+ 'NetworkDisabled' => true,
27
+ 'HostConfig' => {
28
+ 'Binds' => binds},
29
+ 'Volumes' => volumes)
26
30
  end
27
31
 
28
32
  def run!
29
- container.start
30
- container.wait(Mumukit.config.command_time_limit)
31
-
32
- exit = container.json['State']['ExitCode']
33
- out = container.streaming_logs(stdout: true, stderr: true)
33
+ run_container!
34
+ exit, out = fetch_container_state!
34
35
 
35
36
  if exit == 0
36
37
  [out, :passed]
@@ -41,6 +42,17 @@ module Mumukit
41
42
  [I18n.t('mumukit.time_exceeded', limit: Mumukit.config.command_time_limit), :aborted]
42
43
  end
43
44
 
45
+ def run_container!
46
+ container.start
47
+ container.wait(Mumukit.config.command_time_limit)
48
+ end
49
+
50
+ def fetch_container_state!
51
+ exit = container.json['State']['ExitCode']
52
+ out = container.streaming_logs(stdout: true, stderr: true)
53
+ [exit, out]
54
+ end
55
+
44
56
  def destroy!
45
57
  if container
46
58
  container.stop
@@ -48,4 +60,4 @@ module Mumukit
48
60
  end
49
61
  end
50
62
  end
51
- end
63
+ end
@@ -1,10 +1,10 @@
1
1
  module Mumukit::Metatest
2
2
  class Checker
3
- def check(value, example)
4
- example[:postconditions].each { |key, arg| check_assertion key, value, arg, example }
5
- [example[:name], :passed, render_success_output(value)]
3
+ def check(result, example)
4
+ example[:postconditions].each { |key, arg| check_assertion key, result, arg, example }
5
+ [example[:name], :passed, render_success_output(result)]
6
6
  rescue => e
7
- [example[:name], :failed, render_error_output(value, e.message)]
7
+ [example[:name], :failed, render_error_output(result, e.message)]
8
8
  end
9
9
 
10
10
  def render_success_output(value)
@@ -15,8 +15,8 @@ module Mumukit::Metatest
15
15
  error
16
16
  end
17
17
 
18
- def check_assertion(key, value, arg, example)
19
- send "check_#{key}", value, arg
18
+ def check_assertion(key, result, arg, example)
19
+ send "check_#{key}", result, arg
20
20
  end
21
21
 
22
22
  def fail(message)
@@ -0,0 +1,59 @@
1
+ module Mumukit::Metatest
2
+ class InteractiveChecker < Checker
3
+
4
+ def initialize(request)
5
+ @request = request
6
+ end
7
+
8
+ def check_last_query_equals(_result, goal)
9
+ expected = goal[:query]
10
+ actual = @request.query
11
+ fail_t :check_last_query_equals, expected: expected, actual: actual unless expected == actual
12
+ end
13
+
14
+ def check_last_query_matches(_result, goal)
15
+ regex = goal[:regex]
16
+ fail_t :check_last_query_matches, regex: regex.inspect unless regex.matches? @request.query
17
+ end
18
+
19
+ def check_last_query_fails(result, _goal)
20
+ fail_t :check_last_query_fails unless result[:query][:status] == :failed
21
+ end
22
+
23
+ def check_last_query_outputs(result, goal)
24
+ expected = goal.with_indifferent_access[:output]
25
+ actual = result[:query][:result]
26
+ fail_t :check_last_query_outputs, expected: expected, actual: actual unless expected == actual
27
+ end
28
+
29
+ def check_last_query_passes(result, _goal)
30
+ fail_t :check_last_query_passes unless result[:query][:status] == :passed
31
+ end
32
+
33
+ def check_query_passes(result, goal)
34
+ fail_t :check_query_passes, query: goal.with_indifferent_access[:query] unless result[:status] == :passed
35
+ end
36
+
37
+ def check_query_fails(result, goal)
38
+ fail_t :check_query_fails, query: goal.with_indifferent_access[:query] unless result[:status] == :failed
39
+ end
40
+
41
+ def check_query_outputs(result, goal)
42
+ expected = goal.with_indifferent_access[:output]
43
+ actual = result[:goal]
44
+ fail_t :check_query_outputs, query: goal.with_indifferent_access[:query], expected: expected, actual: actual unless expected == actual
45
+ end
46
+
47
+ def render_success_output(_value)
48
+ I18n.t locale(:goal_passed)
49
+ end
50
+
51
+ def fail_t(sym, *args)
52
+ fail I18n.t locale(sym), *args
53
+ end
54
+
55
+ def locale(sym)
56
+ 'mumukit.interactive.' + sym.to_s
57
+ end
58
+ end
59
+ end
@@ -3,5 +3,6 @@ end
3
3
 
4
4
  require_relative './metatest/errors'
5
5
  require_relative './metatest/checker'
6
+ require_relative './metatest/interactive_checker'
6
7
  require_relative './metatest/framework'
7
8
  require_relative './metatest/identity_runner'
@@ -1,3 +1,4 @@
1
+
1
2
  class Mumukit::Runner
2
3
  attr_reader :name, :runtime
3
4
 
@@ -22,6 +23,23 @@ class Mumukit::Runner
22
23
  name.camelize
23
24
  end
24
25
 
26
+ def directives_pipeline
27
+ @pipeline ||= new_directives_pipeline
28
+ end
29
+
30
+ def new_directives_pipeline
31
+ if config.preprocessor_enabled
32
+ Mumukit::Directives::Pipeline.new(
33
+ [Mumukit::Directives::Sections.new,
34
+ Mumukit::Directives::Interpolations.new('test'),
35
+ Mumukit::Directives::Interpolations.new('extra'),
36
+ Mumukit::Directives::Flags.new],
37
+ config.comment_type)
38
+ else
39
+ Mumukit::Directives::NullPipeline
40
+ end
41
+ end
42
+
25
43
  def self.default_config
26
44
  @default_config
27
45
  end
@@ -2,7 +2,7 @@ module Mumukit::RuntimeInfo
2
2
  def info
3
3
  {
4
4
  name: Mumukit.runner_name,
5
- version: (File.read('version') rescue 'master'),
5
+ version: hook_class(:version)::VERSION,
6
6
  escualo_base_version: ENV['ESCUALO_BASE_VERSION'],
7
7
  escualo_service_version: ENV['ESCUALO_SERVICE_VERSION'],
8
8
  mumukit_version: Mumukit::VERSION,
@@ -10,6 +10,7 @@ module Mumukit::RuntimeInfo
10
10
  comment_type: Mumukit.config.comment_type,
11
11
  features: {
12
12
  query: query_hook?,
13
+ try: try_hook?,
13
14
  expectations: expectations_hook?,
14
15
  feedback: feedback_hook?,
15
16
  secure: validation_hook?,
@@ -7,6 +7,10 @@ class Mumukit::Runtime
7
7
  @hook_classes = {}
8
8
  end
9
9
 
10
+ def reset!
11
+ @hook_classes = {}
12
+ end
13
+
10
14
  def hook_defined?(hook_name)
11
15
  hook_name.to_default_mumukit_hook_class rescue raise "Wrong hook #{hook_name}"
12
16
 
@@ -21,8 +25,6 @@ class Mumukit::Runtime
21
25
  hook_class(hook_name).new(@config)
22
26
  end
23
27
 
24
- private
25
-
26
28
  def hook_class(hook_name)
27
29
  @hook_classes[hook_name] ||=
28
30
  if hook_defined? hook_name
@@ -8,10 +8,10 @@ class Symbol
8
8
  end
9
9
 
10
10
  def to_mumukit_hook_class
11
- Kernel.const_get to_mumukit_hook_class_name
11
+ to_mumukit_hook_class_name.constantize
12
12
  end
13
13
 
14
14
  def to_default_mumukit_hook_class
15
- Kernel.const_get "Mumukit::Defaults::#{to_simple_mumukit_hook_class_name}"
15
+ "Mumukit::Defaults::#{to_simple_mumukit_hook_class_name}".constantize
16
16
  end
17
17
  end
@@ -55,6 +55,10 @@ class Mumukit::Server::App < Sinatra::Base
55
55
  JSON.generate(server.query!(parse_request))
56
56
  end
57
57
 
58
+ post '/try' do
59
+ JSON.generate(server.try!(parse_request))
60
+ end
61
+
58
62
  get '/*' do
59
63
  redirect settings.mumuki_url
60
64
  end
@@ -12,6 +12,10 @@ class Mumukit::Server::ResponseBuilder
12
12
  @response.merge!(expectationResults: r) if r.present?
13
13
  end
14
14
 
15
+ def add_try_results(r)
16
+ @response = unstructured_base_response(r)
17
+ @response.merge!(queryResult: r[2])
18
+ end
15
19
 
16
20
  def add_feedback(f)
17
21
  @response.merge!(feedback: f) if f.present?
@@ -0,0 +1,30 @@
1
+ class Mumukit::Server::TestPipeline
2
+ def initialize(server, request)
3
+ @server = server
4
+ @request = request
5
+ end
6
+
7
+ def evaluate!
8
+ @test_results = @server.run_tests! @request
9
+ @expectation_results = @server.run_expectations!(@request).try do |raw|
10
+ static_errors?(raw) ? [] : raw
11
+ end
12
+ end
13
+
14
+ def generate_feedback!
15
+ @feedback = @server.run_feedback! @request,
16
+ struct(test_results: @test_results, expectation_results: @expectation_results)
17
+ end
18
+
19
+ def response
20
+ builder = Mumukit::Server::ResponseBuilder.new
21
+ builder.add_test_results @test_results
22
+ builder.add_expectation_results @expectation_results
23
+ builder.add_feedback @feedback
24
+ builder.build
25
+ end
26
+
27
+ def static_errors?(raw)
28
+ raw.size == 2 && raw[1] == :errored
29
+ end
30
+ end
@@ -32,19 +32,10 @@ class Mumukit::Server::TestServer
32
32
 
33
33
  def test!(request)
34
34
  respond_to(request) do |r|
35
- test_results = run_tests! r
36
- expectation_results = run_expectations! r
37
-
38
- results = OpenStruct.new(test_results: test_results,
39
- expectation_results: expectation_results)
40
-
41
- feedback = run_feedback! r, results
42
-
43
- Mumukit::Server::ResponseBuilder.build do
44
- add_test_results(test_results)
45
- add_expectation_results(expectation_results)
46
- add_feedback(feedback)
47
- end
35
+ pipeline = Mumukit::Server::TestPipeline.new self, r
36
+ pipeline.evaluate!
37
+ pipeline.generate_feedback!
38
+ pipeline.response
48
39
  end
49
40
  end
50
41
 
@@ -57,8 +48,13 @@ class Mumukit::Server::TestServer
57
48
  end
58
49
  end
59
50
 
60
- def run_query!(request)
61
- compile_and_run runtime.query_hook, request
51
+ def try!(request)
52
+ respond_to(request) do |r|
53
+ results = run_try!(r)
54
+ Mumukit::Server::ResponseBuilder.build do
55
+ add_try_results(results)
56
+ end
57
+ end
62
58
  end
63
59
 
64
60
  def run_tests!(request)
@@ -68,11 +64,9 @@ class Mumukit::Server::TestServer
68
64
  end
69
65
 
70
66
  def run_expectations!(request)
71
- if request.expectations
72
- compile_and_run runtime.expectations_hook, request
73
- else
74
- []
75
- end
67
+ return [] if request.expectations.nil? || request.content.nil?
68
+
69
+ compile_and_run runtime.expectations_hook, request
76
70
  end
77
71
 
78
72
  def run_feedback!(request, results)
@@ -81,26 +75,23 @@ class Mumukit::Server::TestServer
81
75
 
82
76
  private
83
77
 
78
+ def run_query!(request)
79
+ compile_and_run runtime.query_hook, request
80
+ end
81
+
82
+ def run_try!(request)
83
+ compile_and_run runtime.try_hook, request
84
+ end
85
+
84
86
  def compile_and_run(hook, request)
85
- compilation = hook.compile(preprocess request)
87
+ compilation = hook.compile(request)
86
88
  hook.run!(compilation)
89
+ rescue Mumukit::CompilationError => e
90
+ [e.message, :errored]
87
91
  end
88
92
 
89
93
  def preprocess(request)
90
- if Mumukit.config.preprocessor_enabled
91
- directives_pipeline.transform(request)
92
- else
93
- request
94
- end
95
- end
96
-
97
- def directives_pipeline
98
- @pipeline ||= Mumukit::Directives::Pipeline.new(
99
- [Mumukit::Directives::Sections.new,
100
- Mumukit::Directives::Interpolations.new('test'),
101
- Mumukit::Directives::Interpolations.new('extra'),
102
- Mumukit::Directives::Flags.new],
103
- Mumukit.config.comment_type)
94
+ Mumukit.directives_pipeline.transform(request)
104
95
  end
105
96
 
106
97
  def validate_request!(request)
@@ -108,7 +99,7 @@ class Mumukit::Server::TestServer
108
99
  end
109
100
 
110
101
  def respond_to(request)
111
- yield request.tap { |r| validate_request! r }
102
+ yield preprocess request.tap { |r| validate_request! r }
112
103
  rescue Mumukit::RequestValidationError => e
113
104
  {exit: :aborted, out: e.message}
114
105
  rescue Exception => e
@@ -4,4 +4,5 @@ module Mumukit
4
4
  end
5
5
 
6
6
  require_relative './server/response_builder'
7
+ require_relative './server/test_pipeline'
7
8
  require_relative './server/test_server'
@@ -2,17 +2,25 @@ module Mumukit
2
2
  class Templates::FileHook < Mumukit::Hook
3
3
  include Mumukit::WithTempfile
4
4
 
5
+ attr_accessor :request
6
+
7
+
5
8
  def compile(request)
9
+ self.request = request
6
10
  write_tempfile! compile_file_content(request)
7
11
  end
8
12
 
9
13
  def run!(file)
10
14
  result, status = run_file!(file)
11
- post_process_file(file, result.force_encoding('UTF-8'), status)
15
+ post_process_file(file, cleanup_raw_result(result), status)
12
16
  ensure
13
17
  file.unlink
14
18
  end
15
19
 
20
+ def cleanup_raw_result(result)
21
+ mask_tempfile_references(result.force_encoding('UTF-8'), masked_tempfile_path)
22
+ end
23
+
16
24
  def post_process_file(file, result, status)
17
25
  [result, status]
18
26
  end
@@ -22,8 +30,29 @@ module Mumukit
22
30
 
23
31
  required :run_file!, 'Wrong configuration. You must include an environment mixin'
24
32
 
25
- def self.structured(value=true)
33
+ def masked_tempfile_path
34
+ @masked_tempfile_path ||= "#{t 'mumukit.masked_tempfile_basename'}#{tempfile_extension}"
35
+ end
36
+
37
+ def self.line_number_offset(offset, options={})
38
+ include Mumukit::Templates::WithLineNumberOffset
39
+
40
+ define_method(:line_number_offset) do
41
+ extra_offset = options[:include_extra] && request.extra ? request.extra.lines.length : 0
42
+ offset + extra_offset
43
+ end
44
+ end
45
+
46
+ def self.metatested(value=true)
47
+ if value
48
+ include Mumukit::Templates::WithMetatest
49
+ include Mumukit::Templates::WithMetatestResults
50
+ end
51
+ end
52
+
53
+ def self.structured(value=true, separator: nil)
26
54
  if value
55
+ @separator = separator
27
56
  include Mumukit::Templates::WithStructuredResults
28
57
  end
29
58
  end
@@ -36,8 +65,9 @@ module Mumukit
36
65
  end
37
66
  end
38
67
 
39
- def self.mashup(*args)
68
+ def self.mashup(*args, &map_block)
40
69
  include Mumukit::Templates::WithMashupFileContent
70
+ define_method(:map_mashup_fields, &map_block) if block_given?
41
71
 
42
72
  args = args.present? ? args : [:extra, :content, :test]
43
73
 
@@ -1,32 +1,71 @@
1
+ require 'mumukit/inspection'
2
+ require 'mulang'
3
+
1
4
  module Mumukit
2
- class Templates::MulangExpectationsHook < Mumukit::Templates::FileHook
3
- isolated false
5
+ class Templates::MulangExpectationsHook < Mumukit::Hook
6
+ LOGIC_SMELLS = %w(UsesCut UsesFail UsesUnificationOperator HasRedundantReduction)
7
+ FUNCTIONAL_SMELLS = %w(HasRedundantParameter HasRedundantGuards)
8
+ OBJECT_ORIENTED_SMELLS = %w(DoesNullTest ReturnsNull)
9
+ IMPERATIVE_SMELLS = %w(HasRedundantLocalVariableReturn HasAssignmentReturn)
10
+ EXPRESSIVENESS_SMELLS = %w(HasTooShortBindings HasWrongCaseBindings HasMisspelledBindings)
11
+ GENERIC_SMELLS = %w(IsLongCode HasCodeDuplication HasRedundantLambda HasRedundantIf DoesTypeTest HasRedundantBooleanComparison)
4
12
 
5
13
  required :language, 'You have to provide a Mulang-compatible language in order to use this hook'
6
14
 
7
- def tempfile_extension
8
- '.json'
15
+ def compile(request)
16
+ expectations, exceptions = compile_expectations_and_exceptions request
17
+ mulang_code(request).analysis(
18
+ expectations: expectations,
19
+ smellsSet: {
20
+ tag: 'AllSmells',
21
+ exclude: (exceptions + default_smell_exceptions)
22
+ },
23
+ domainLanguage: domain_language)
24
+ end
25
+
26
+ def run!(analysis)
27
+ parse_response Mulang.analyse(analysis)
28
+ rescue JSON::ParserError
29
+ raise Mumukit::CompilationError, "Can not handle mulang results for analysis #{analysis}"
9
30
  end
10
31
 
11
- def command_line(filename)
12
- "cat #{filename} | #{mulang_path} -s"
32
+
33
+ def domain_language
34
+ {
35
+ caseStyle: "CamelCase",
36
+ minimumIdentifierSize: 3,
37
+ jargon: []
38
+ }
13
39
  end
14
40
 
15
- def post_process_file(file, result, status)
16
- parse_response JSON.pretty_parse(result)
41
+ def default_smell_exceptions
42
+ []
17
43
  end
18
44
 
19
- def compile_file_content(request)
20
- compile_json_file_content(request).to_json
45
+ def mulang_code(request)
46
+ Mulang::Code.new(mulang_language, compile_content(request[:content]))
21
47
  end
22
48
 
23
- def compile_json_file_content(request)
24
- {
25
- expectations: request[:expectations].map { |it| compile_expectation(it.deep_symbolize_keys) },
26
- code: {
27
- content: compile_content(request[:content]),
28
- language: language}
29
- }
49
+ def mulang_language
50
+ language == 'Mulang' ? Mulang::Language::External.new : Mulang::Language::Native.new(language)
51
+ end
52
+
53
+ def compile_expectations_and_exceptions(request)
54
+ expectations = []
55
+ exceptions = []
56
+ request[:expectations].each do |it|
57
+ fill_expectations_and_excetions it.deep_symbolize_keys, expectations, exceptions
58
+ end
59
+ [expectations, exceptions]
60
+ end
61
+
62
+ def fill_expectations_and_excetions(expectation, expectations, exceptions)
63
+ inspection = expectation[:inspection]
64
+ if inspection&.start_with? 'Except:'
65
+ exceptions << inspection[7..-1]
66
+ else
67
+ expectations << compile_expectation(expectation)
68
+ end
30
69
  end
31
70
 
32
71
  def compile_content(content)
@@ -34,11 +73,14 @@ module Mumukit
34
73
  end
35
74
 
36
75
  def compile_expectation(expectation)
37
- (expectation[:verb].present? ? {tag: :Advanced} : {tag: :Basic}).merge(expectation)
76
+ Mumukit::Inspection::Expectation.parse(expectation).as_v2.to_h
38
77
  end
39
78
 
40
79
  def parse_response(response)
41
- response['results'].map do |it|
80
+ if response['tag'] == 'AnalysisFailed'
81
+ raise Mumukit::CompilationError, response['reason']
82
+ end
83
+ response['expectationResults'].map do |it|
42
84
  {result: it['result'],
43
85
  expectation: parse_expectation(it['expectation'])}
44
86
  end
@@ -54,4 +96,4 @@ module Mumukit
54
96
  end
55
97
  end
56
98
  end
57
- end
99
+ end
@@ -0,0 +1,17 @@
1
+ module Mumukit
2
+ class Templates::TryHook < Templates::FileHook
3
+ def compile(request)
4
+ @goal = {postconditions: [[request.goal.with_indifferent_access[:kind], request.goal]]}
5
+ @checker = Metatest::InteractiveChecker.new request
6
+ super request
7
+ end
8
+
9
+ def post_process_file(file, result, status)
10
+ structured_results = to_structured_results(file, result, status)
11
+ check_results = @checker.check structured_results, @goal
12
+ [check_results[2], check_results[1], structured_results[:query]]
13
+ end
14
+
15
+ required :to_structured_results
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ module Mumukit::Templates::WithCookie
2
+ def build_cookie_code(request)
3
+ @cookie = cookie_class.new(request.cookie)
4
+ @cookie.code
5
+ end
6
+
7
+ def trim_cookie_output(output)
8
+ @cookie.trim(output)
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ module Mumukit::Templates::WithLineNumberOffset
2
+ def cleanup_raw_result(result)
3
+ super(result).gsub(line_number_reference_regexp) do
4
+ rebuild_line_number_reference correct_line_number($1.to_i)
5
+ end
6
+ end
7
+
8
+ def line_number_reference_regexp
9
+ /#{masked_tempfile_path}\:(\d+)/m
10
+ end
11
+
12
+ def rebuild_line_number_reference(new_line_number)
13
+ "#{masked_tempfile_path}:#{new_line_number}"
14
+ end
15
+
16
+ def correct_line_number(number)
17
+ number - line_number_offset
18
+ end
19
+ end
@@ -1,11 +1,15 @@
1
1
  module Mumukit
2
2
  module Templates::WithMashupFileContent
3
3
  def compile_file_content(request)
4
- mashup_fields.map { |field| request.send field }.join("\n")
4
+ map_mashup_fields(*mashup_fields.map { |field| request.public_send field }).join("\n")
5
5
  end
6
6
 
7
7
  def mashup_fields
8
8
  raise 'must define mashup fields'
9
9
  end
10
+
11
+ def map_mashup_fields(*fields)
12
+ fields
13
+ end
10
14
  end
11
15
  end
@@ -0,0 +1,14 @@
1
+ module Mumukit::Templates::WithMetatest
2
+ def metatest
3
+ Mumukit::Metatest::Framework.new checker: metatest_checker,
4
+ runner: metatest_runner
5
+ end
6
+
7
+ def metatest_runner
8
+ Mumukit::Metatest::IdentityRunner.new
9
+ end
10
+
11
+ def run_metatest!(target, examples)
12
+ metatest.test target, examples
13
+ end
14
+ end
@@ -0,0 +1,31 @@
1
+ module Mumukit::Templates::WithMetatestResults
2
+ def compile_file_content(request)
3
+ @examples = compile_metatest_examples(request)
4
+ compile_metatest_file_content(request)
5
+ end
6
+
7
+ def compile_metatest_examples(request)
8
+ YAML.load(request.test).deep_symbolize_keys[:examples]
9
+ end
10
+
11
+ def post_process_file(file, result, status)
12
+ if status == :passed
13
+ run_metatest! to_metatest_compilation(result), @examples
14
+ else
15
+ post_process_unsuccessful_result(file, result, status)
16
+ end
17
+ rescue JSON::ParserError
18
+ [result, :errored]
19
+ end
20
+
21
+ def post_process_unsuccessful_result(_file, result, status)
22
+ [result, status]
23
+ end
24
+
25
+ def to_metatest_compilation(result)
26
+ JSON.pretty_parse(result).deep_symbolize_keys
27
+ end
28
+ end
29
+
30
+
31
+
@@ -1,15 +1,26 @@
1
1
  module Mumukit::Templates::WithStructuredResults
2
2
  def post_process_file(file, result, status)
3
+ result = extract_result(result)
3
4
  if [:passed, :failed].include? status
4
5
  [to_structured_result(result)]
5
6
  else
6
- [result, status]
7
+ post_process_unstructured_result(file, result, status)
7
8
  end
8
9
  rescue JSON::ParserError
9
10
  [result, :errored]
10
11
  end
11
12
 
13
+ def post_process_unstructured_result(_file, result, status)
14
+ [result, status]
15
+ end
16
+
17
+ def extract_result(result)
18
+ separator = self.class.instance_variable_get :@separator
19
+ result = result.split(separator).last if separator.present?
20
+ result
21
+ end
22
+
12
23
  def to_structured_result(result)
13
24
  JSON.pretty_parse(result)
14
25
  end
15
- end
26
+ end
@@ -4,9 +4,14 @@ module Mumukit
4
4
  end
5
5
 
6
6
  require_relative './templates/file_hook'
7
+ require_relative './templates/try_hook'
7
8
  require_relative './templates/with_structured_results'
9
+ require_relative './templates/with_metatest'
10
+ require_relative './templates/with_metatest_results'
8
11
  require_relative './templates/with_mashup_file_content'
9
12
  require_relative './templates/with_embedded_environment'
10
13
  require_relative './templates/with_isolated_environment'
14
+ require_relative './templates/with_line_number_offset'
11
15
  require_relative './templates/mulang_expectations_hook'
12
16
  require_relative './templates/with_code_smells'
17
+ require_relative './templates/with_cookie'
@@ -1,3 +1,3 @@
1
1
  module Mumukit
2
- VERSION = '2.4.0'
2
+ VERSION = '2.17.0'
3
3
  end
@@ -23,5 +23,8 @@ module Mumukit
23
23
  file
24
24
  end
25
25
 
26
+ def mask_tempfile_references(string, masked_tempfile_path)
27
+ string.gsub(/\/tmp\/mumuki\.compile(.+?)#{tempfile_extension}/, masked_tempfile_path)
28
+ end
26
29
  end
27
30
  end
data/lib/mumukit.rb CHANGED
@@ -1,15 +1,8 @@
1
- require 'i18n'
2
- require 'i18n/backend/fallbacks'
3
1
  require 'mumukit/core'
4
2
  require 'mumukit/directives'
5
3
  require 'ostruct'
6
4
 
7
- pwd = File.expand_path(File.dirname(__FILE__))
8
-
9
- I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
10
- I18n.load_path += Dir[File.join(pwd, 'locales', '*.yml')]
11
-
12
- I18n.backend.load_translations
5
+ I18n.load_translations_path File.join(__dir__, 'locales', '*.yml')
13
6
 
14
7
  module Mumukit
15
8
  def self.current_runner=(runner)
@@ -34,14 +27,14 @@ module Mumukit
34
27
  end
35
28
 
36
29
  class << self
37
- delegate :prefix, :config, :configure, :runtime, :configure_runtime, to: :current_runner
30
+ delegate :prefix, :config, :configure, :runtime, :configure_runtime, :directives_pipeline, to: :current_runner
38
31
  end
39
32
  end
40
33
 
41
34
  require_relative 'mumukit/runner'
42
35
 
43
36
  Mumukit.configure_defaults do |config|
44
- config.limit_script = File.join(pwd, '..', 'bin', 'limit')
37
+ config.limit_script = File.join(__dir__, '..', 'bin', 'limit')
45
38
  config.content_type = :plain
46
39
  config.comment_type = Mumukit::Directives::CommentType::Cpp
47
40
  config.preprocessor_enabled = true
@@ -55,6 +48,7 @@ require 'mumukit/content_type'
55
48
 
56
49
  require_relative 'mumukit/version'
57
50
  require_relative 'mumukit/env'
51
+ require_relative 'mumukit/cookie'
58
52
  require_relative 'mumukit/with_tempfile'
59
53
  require_relative 'mumukit/with_content_type'
60
54
  require_relative 'mumukit/with_command_line'
@@ -63,6 +57,8 @@ require_relative 'mumukit/runtime'
63
57
  require_relative 'mumukit/isolated_environment'
64
58
  require_relative 'mumukit/templates'
65
59
  require_relative 'mumukit/request_validation_error'
60
+ require_relative 'mumukit/compilation_error'
66
61
  require_relative 'mumukit/defaults'
67
62
  require_relative 'mumukit/metatest'
63
+ require_relative 'mumukit/explainer'
68
64
  require_relative 'mumukit/server'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mumukit
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Franco Leonardo Bulgarelli
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-11 00:00:00.000000000 Z
11
+ date: 2017-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: sinatra
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -151,47 +165,87 @@ dependencies:
151
165
  - !ruby/object:Gem::Version
152
166
  version: '0.46'
153
167
  - !ruby/object:Gem::Dependency
154
- name: mumukit-core
168
+ name: mulang
155
169
  requirement: !ruby/object:Gem::Requirement
156
170
  requirements:
157
171
  - - "~>"
158
172
  - !ruby/object:Gem::Version
159
- version: '0.1'
173
+ version: '3.3'
160
174
  type: :runtime
161
175
  prerelease: false
162
176
  version_requirements: !ruby/object:Gem::Requirement
163
177
  requirements:
164
178
  - - "~>"
165
179
  - !ruby/object:Gem::Version
166
- version: '0.1'
180
+ version: '3.3'
181
+ - !ruby/object:Gem::Dependency
182
+ name: mumukit-inspection
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '3.1'
188
+ type: :runtime
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '3.1'
195
+ - !ruby/object:Gem::Dependency
196
+ name: mumukit-core
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0.2'
202
+ - - "<"
203
+ - !ruby/object:Gem::Version
204
+ version: '2'
205
+ type: :runtime
206
+ prerelease: false
207
+ version_requirements: !ruby/object:Gem::Requirement
208
+ requirements:
209
+ - - ">="
210
+ - !ruby/object:Gem::Version
211
+ version: '0.2'
212
+ - - "<"
213
+ - !ruby/object:Gem::Version
214
+ version: '2'
167
215
  - !ruby/object:Gem::Dependency
168
216
  name: mumukit-directives
169
217
  requirement: !ruby/object:Gem::Requirement
170
218
  requirements:
171
219
  - - "~>"
172
220
  - !ruby/object:Gem::Version
173
- version: '0.1'
221
+ version: '0.3'
174
222
  type: :runtime
175
223
  prerelease: false
176
224
  version_requirements: !ruby/object:Gem::Requirement
177
225
  requirements:
178
226
  - - "~>"
179
227
  - !ruby/object:Gem::Version
180
- version: '0.1'
228
+ version: '0.3'
181
229
  - !ruby/object:Gem::Dependency
182
230
  name: mumukit-content-type
183
231
  requirement: !ruby/object:Gem::Requirement
184
232
  requirements:
185
- - - "~>"
233
+ - - ">="
186
234
  - !ruby/object:Gem::Version
187
235
  version: '0.2'
236
+ - - "<"
237
+ - !ruby/object:Gem::Version
238
+ version: '2'
188
239
  type: :runtime
189
240
  prerelease: false
190
241
  version_requirements: !ruby/object:Gem::Requirement
191
242
  requirements:
192
- - - "~>"
243
+ - - ">="
193
244
  - !ruby/object:Gem::Version
194
245
  version: '0.2'
246
+ - - "<"
247
+ - !ruby/object:Gem::Version
248
+ version: '2'
195
249
  description: Helpers for building a Mumuki Test Server
196
250
  email:
197
251
  - flbulgarelli@yahoo.com.ar
@@ -200,18 +254,22 @@ extensions: []
200
254
  extra_rdoc_files: []
201
255
  files:
202
256
  - bin/limit
203
- - bin/mulang
204
257
  - lib/locales/en.yml
205
258
  - lib/locales/es.yml
206
259
  - lib/mumukit.rb
260
+ - lib/mumukit/compilation_error.rb
261
+ - lib/mumukit/cookie.rb
207
262
  - lib/mumukit/defaults.rb
208
263
  - lib/mumukit/defaults/default_expectations_hook.rb
209
264
  - lib/mumukit/defaults/default_feedback_hook.rb
210
265
  - lib/mumukit/defaults/default_metadata_hook.rb
211
266
  - lib/mumukit/defaults/default_query_hook.rb
212
267
  - lib/mumukit/defaults/default_test_hook.rb
268
+ - lib/mumukit/defaults/default_try_hook.rb
213
269
  - lib/mumukit/defaults/default_validation_hook.rb
270
+ - lib/mumukit/defaults/default_version_hook.rb
214
271
  - lib/mumukit/env.rb
272
+ - lib/mumukit/explainer.rb
215
273
  - lib/mumukit/hook.rb
216
274
  - lib/mumukit/isolated_environment.rb
217
275
  - lib/mumukit/metatest.rb
@@ -219,6 +277,7 @@ files:
219
277
  - lib/mumukit/metatest/errors.rb
220
278
  - lib/mumukit/metatest/framework.rb
221
279
  - lib/mumukit/metatest/identity_runner.rb
280
+ - lib/mumukit/metatest/interactive_checker.rb
222
281
  - lib/mumukit/request_validation_error.rb
223
282
  - lib/mumukit/runner.rb
224
283
  - lib/mumukit/runtime.rb
@@ -229,14 +288,20 @@ files:
229
288
  - lib/mumukit/server.rb
230
289
  - lib/mumukit/server/app.rb
231
290
  - lib/mumukit/server/response_builder.rb
291
+ - lib/mumukit/server/test_pipeline.rb
232
292
  - lib/mumukit/server/test_server.rb
233
293
  - lib/mumukit/templates.rb
234
294
  - lib/mumukit/templates/file_hook.rb
235
295
  - lib/mumukit/templates/mulang_expectations_hook.rb
296
+ - lib/mumukit/templates/try_hook.rb
236
297
  - lib/mumukit/templates/with_code_smells.rb
298
+ - lib/mumukit/templates/with_cookie.rb
237
299
  - lib/mumukit/templates/with_embedded_environment.rb
238
300
  - lib/mumukit/templates/with_isolated_environment.rb
301
+ - lib/mumukit/templates/with_line_number_offset.rb
239
302
  - lib/mumukit/templates/with_mashup_file_content.rb
303
+ - lib/mumukit/templates/with_metatest.rb
304
+ - lib/mumukit/templates/with_metatest_results.rb
240
305
  - lib/mumukit/templates/with_structured_results.rb
241
306
  - lib/mumukit/version.rb
242
307
  - lib/mumukit/with_command_line.rb
@@ -263,7 +328,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
263
328
  version: '0'
264
329
  requirements: []
265
330
  rubyforge_project:
266
- rubygems_version: 2.4.8
331
+ rubygems_version: 2.7.2
267
332
  signing_key:
268
333
  specification_version: 4
269
334
  summary: Mumuki Test Server Development Kit
data/bin/mulang DELETED
Binary file