Merge: nitweb: use ConsoleLog middleware
authorJean Privat <jean@pryen.org>
Mon, 6 Jun 2016 15:12:57 +0000 (11:12 -0400)
committerJean Privat <jean@pryen.org>
Mon, 6 Jun 2016 15:12:57 +0000 (11:12 -0400)
Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

Pull-Request: #2163
Reviewed-by: Jean Privat <jean@pryen.org>

46 files changed:
contrib/pep8analysis/package.ini
share/man/nitunit.md
share/nitweb/directives/contributor-list.html [new file with mode: 0644]
share/nitweb/index.html
share/nitweb/javascripts/entities.js
share/nitweb/javascripts/index.js [new file with mode: 0644]
share/nitweb/javascripts/model.js
share/nitweb/javascripts/nitweb.js
share/nitweb/stylesheets/nitweb.css
share/nitweb/views/index.html
src/loader.nit
src/modelbuilder_base.nit
src/nitls.nit
src/nitunit.nit
src/nitweb.nit
src/package.ini
src/testing/testing_base.nit
src/testing/testing_suite.nit
src/web/api_catalog.nit [new file with mode: 0644]
src/web/model_api.nit
src/web/web.nit
src/web/web_base.nit
tests/nitls.args
tests/nitpick.args
tests/project1/subdir/subdir2/subdir3/submodule.nit [new file with mode: 0644]
tests/project1/uselessdir/uselessdir2/uselessfile [new file with mode: 0644]
tests/sav/nitdoc_args4.res
tests/sav/nitls_args1.res
tests/sav/nitls_args2.res
tests/sav/nitls_args3.res
tests/sav/nitls_args4.res
tests/sav/nitls_args6.res
tests/sav/nitls_args7.res [new file with mode: 0644]
tests/sav/nitls_args8.res [new file with mode: 0644]
tests/sav/nitunit_args9.res
tests/sav/test_basename_perf_args1.res
tests/sav/test_neo_args1.res
tests/sav/test_sort_perf_args1.res
tests/test_nitunit4/sav/test_sav_conflict.res [new file with mode: 0644]
tests/test_nitunit4/test_baz.res [moved from tests/test_nitunit4/test_nitunit4.sav/test_baz.res with 100% similarity]
tests/test_nitunit4/test_nitunit4.nit
tests/test_nitunit4/test_nitunit4.sav/test_sav_conflict.res [new file with mode: 0644]
tests/test_nitunit4/test_sav_conflict.res [new file with mode: 0644]
tests/test_prog/game/excluded.nit [new file with mode: 0644]
tests/test_prog/game/excluded_dir/more.nit [new file with mode: 0644]
tests/test_prog/package.ini

index 43d5e4a..bbf3802 100644 (file)
@@ -3,6 +3,8 @@ name=pep8analysis
 tags=educ,web,cli
 maintainer=Alexis Laferrière <alexis.laf@xymus.net>
 license=Apache-2.0
+[source]
+exclude=src/parser/parser_abs.nit
 [upstream]
 browse=https://github.com/nitlang/nit/tree/master/contrib/pep8analysis/
 git=https://github.com/nitlang/nit.git
index 6f09402..23b7382 100644 (file)
@@ -173,7 +173,23 @@ end
 
 Sometimes, it is easier to validate a `TestCase` by comparing its output with a text file containing the expected result.
 
-For each TestCase `test_bar` of a TestSuite `test_mod.nit`, if the corresponding file `test_mod.sav/test_bar.res` exists, then the output of the test is compared with the file.
+For each TestCase `test_bar` of a TestSuite `test_mod.nit`, a corresponding file with the expected output is looked for:
+
+* "test_mod.sav/test_bar.res". I.e. test-cases grouped by test-suites.
+
+       This is the default and is useful if there is a lot of test-suites and test-cases in a directory
+
+* "sav/test_bar.res". I.e. all test-cases grouped in a common sub-directory.
+
+       Useful if there is a lot of test-suites OR test-cases in a directory.
+
+* "test_bar.res" raw in the directory.
+
+       Useful is there is a few test-suites and test-cases in a directory.
+
+All 3 are exclusive. If more than one exists, the test-case is failed.
+
+If a corresponding file then the output of the test-case is compared with the file.
 
 The `diff(1)` command is used to perform the comparison.
 The test is failed if non-zero is returned by `diff`.
@@ -195,6 +211,9 @@ Hello!
 
 If no corresponding `.res` file exists, then the output of the TestCase is ignored.
 
+To helps the management of the expected results, the option `--autosav` can be used to automatically create and update them.
+
+
 ## Configuring TestSuites
 
 `TestSuites` also provide methods to configure the test run:
@@ -292,6 +311,16 @@ Examples: `TestFoo`, `TestFoo*`, `TestFoo::test_foo`, `TestFoo::test_foo*`, `tes
 ### `-t`, `--target-file`
 Specify test suite location.
 
+### `--autosav`
+Automatically create/update .res files for black box testing.
+
+If a black block test fails because a difference between the expected result and the current result then the expected result file is updated (and the test is passed).
+
+If a test-case of a test-suite passes but that some output is generated, then an expected result file is created.
+
+It is expected that the created/updated files are checked since the tests are considered passed.
+A VCS like `git` is often a good tool to check the creation and modification of those files.
+
 ## SUITE GENERATION
 
 ### `--gen-suite`
diff --git a/share/nitweb/directives/contributor-list.html b/share/nitweb/directives/contributor-list.html
new file mode 100644 (file)
index 0000000..a46cce9
--- /dev/null
@@ -0,0 +1,11 @@
+<div ng-if='listContributors.length > 0'>
+       <h3 id={{listId}}>
+               <span>{{listTitle}}</span>
+       </h3>
+       <ul class='list-unstyled user-list'>
+               <li ng-repeat='contributor in listContributors'>
+                       <img class='avatar' src="https://secure.gravatar.com/avatar/{{contributor.hash}}?size=20&amp;default=retro">
+                       {{contributor.name}}
+               </li>
+       </ul>
+</div>
index 17bcd23..1b99a81 100644 (file)
@@ -56,5 +56,6 @@
                <script src='/javascripts/model.js'></script>
                <script src='/javascripts/entities.js'></script>
                <script src='/javascripts/ui.js'></script>
+               <script src='/javascripts/index.js'></script>
        </body>
 </html>
index 255602c..9d9a452 100644 (file)
@@ -94,6 +94,7 @@
                                restrict: 'E',
                                scope: {
                                        listEntities: '=',
+                                       listId: '@',
                                        listTitle: '@',
                                        listObjectFilter: '=',
                                },
diff --git a/share/nitweb/javascripts/index.js b/share/nitweb/javascripts/index.js
new file mode 100644 (file)
index 0000000..534f462
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016 Alexandre Terrasa <alexandre@moz-code.org>.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function() {
+       angular
+               .module('index', ['model', 'ngSanitize'])
+
+               .run(['$anchorScroll', function($anchorScroll) {
+                       $anchorScroll.yOffset = 80;
+               }])
+
+               .controller('IndexCtrl', ['Catalog', '$routeParams', '$sce', '$scope', '$location', '$anchorScroll', function(Catalog, $routeParams, $sce, $scope, $location, $anchorScroll) {
+                       this.loadHighlighted = function() {
+                               Catalog.loadHightlighted(
+                                       function(data) {
+                                               $scope.highlighted = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+
+                       this.loadMostRequired = function() {
+                               Catalog.loadMostRequired(
+                                       function(data) {
+                                               $scope.required = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+
+                       this.loadByTags = function() {
+                               Catalog.loadByTags(
+                                       function(data) {
+                                               $scope.bytags = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+
+                       this.loadStats = function() {
+                               Catalog.loadStats(
+                                       function(data) {
+                                               $scope.stats = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+
+                       this.loadContributors = function() {
+                               Catalog.loadContributors(
+                                       function(data) {
+                                               $scope.contributors = data;
+                                       }, function(err) {
+                                               $scope.error = err;
+                                       });
+                       };
+
+
+                       this.scrollTo = function(hash) {
+                               $anchorScroll(hash);
+                       }
+
+                       this.loadHighlighted();
+                       this.loadStats();
+                       this.loadContributors();
+               }])
+
+               .directive('contributorList', ['Model', function(Model) {
+                       return {
+                               restrict: 'E',
+                               scope: {
+                                       listId: '@',
+                                       listTitle: '@',
+                                       listContributors: '='
+                               },
+                               templateUrl: '/directives/contributor-list.html'
+                       };
+               }])
+})();
index 7d5051b..3bf927c 100644 (file)
@@ -22,6 +22,7 @@
 
                .factory('Model', [ '$http', function($http) {
                        return {
+
                                loadEntity: function(id, cb, cbErr) {
                                        $http.get(apiUrl + '/entity/' + id)
                                                .success(cb)
                                }
                        };
                }])
+
+               .factory('Catalog', [ '$http', function($http) {
+                       return {
+                               loadHightlighted: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/catalog/highlighted')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+
+                               loadMostRequired: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/catalog/required')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+
+                               loadByTags: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/catalog/bytags')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+
+                               loadStats: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/catalog/stats')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+
+                               loadContributors: function(cb, cbErr) {
+                                       $http.get(apiUrl + '/catalog/contributors')
+                                               .success(cb)
+                                               .error(cbErr);
+                               },
+                       }
+               }])
 })();
index 28291fb..93b7c3a 100644 (file)
  */
 
 (function() {
-       angular.module('nitweb', ['ngRoute', 'ngSanitize', 'entities'])
+       angular.module('nitweb', ['ngRoute', 'ngSanitize', 'entities', 'index'])
 
        .config(function($routeProvider, $locationProvider) {
                $routeProvider
                        .when('/', {
-                               templateUrl: 'views/index.html'
+                               templateUrl: 'views/index.html',
+                               controller: 'IndexCtrl',
+                               controllerAs: 'indexCtrl'
                        })
                        .when('/package/:id', {
                                templateUrl: 'views/package.html',
index 2d38457..eb82ee6 100644 (file)
@@ -206,6 +206,14 @@ entity-list:hover .btn-filter {
        margin-bottom: 0px;
 }
 /*
+ * Users
+ */
+
+.avatar {
+       border-radius: 2px;
+}
+
+/*
  * Code Highlighting
  */
 
index 9f9b93d..b7bee8e 100644 (file)
@@ -1 +1,70 @@
-<h1>Hello nitweb!</h1>
+<div class='container-fluid'>
+       <div class='page-header'>
+               <h2>Welcome to NitWeb!</h2>
+               <p class='text-muted'>The Nit knowledge base.</p>
+       </div>
+
+       <ul class='nav nav-tabs' role='tablist'>
+               <li role='presentation' class='active'>
+                       <a data-toggle='tab' role='tab' data-target='#highlighted' aria-controls='highlighted'>
+                               <span class='glyphicon glyphicon-book'/> Highlighed
+                       </a>
+               </li>
+               <li role='presentation'>
+                       <a data-toggle='tab' role='tab' data-target='#required' aria-controls='required'
+                               ng-click='indexCtrl.loadMostRequired()'>
+                               <span class='glyphicon glyphicon-book'/> Most required
+                       </a>
+               </li>
+               <li role='presentation'>
+                       <a data-toggle='tab' role='tab' data-target='#bytags' aria-controls='bytags'
+                               ng-click='indexCtrl.loadByTags()'>
+                               <span class='glyphicon glyphicon-book'/> By tags
+                       </a>
+               </li>
+       </ul>
+       <table class='table'>
+               <tr>
+                       <td ng-repeat='(key, value) in stats'>
+                               <h5><strong>{{value}}</strong>&nbsp;<span>{{key}}</span></h5>
+                       </td>
+               </tr>
+       </table>
+
+       <div class='container-fluid'>
+               <div class='col-xs-9'>
+                       <div class='tab-content'>
+                               <div role='tabpanel' class='tab-pane fade in active' id='highlighted'>
+                                       <entity-list list-title='Highlighted packages'
+                                               list-entities='highlighted'
+                                               list-object-filter='{}' />
+                               </div>
+                               <div role='tabpanel' class='tab-pane fade' id='required'>
+                                       <entity-list list-title='Most required'
+                                               list-entities='required'
+                                               list-object-filter='{}' />
+                               </div>
+                               <div role='tabpanel' class='tab-pane fade' id='bytags'>
+                                       <h3>Tags</h3>
+                                       <div class='container-fluid'>
+                                               <div class='col-xs-3' ng-repeat='(tag, packages) in bytags'>
+                                                       <span class='badge'>{{packages.length}}</span>
+                                                       <a ng-click='indexCtrl.scrollTo(tag)'>{{tag}}</a>
+                                               </div>
+                                       </div>
+                                       <div ng-repeat='(tag, packages) in bytags'>
+                                               <entity-list list-id='{{tag}}' list-title='{{tag}}'
+                                                       list-entities='packages'
+                                                       list-object-filter='{}' />
+                                       </div>
+                               </div>
+                       </div>
+               </div>
+               <div class='col-xs-3'>
+                       <contributor-list list-title='Maintainers'
+                                       list-contributors='contributors.maintainers' />
+                       <contributor-list list-title='Contributors'
+                                       list-contributors='contributors.contributors' />
+               </div>
+       </div>
+</div>
index 967d335..d1a4ce5 100644 (file)
@@ -379,6 +379,13 @@ redef class ModelBuilder
                var mgrouppath = path.join_path("..").simplify_path
                var mgroup = identify_group(mgrouppath)
 
+               if mgroup != null then
+                       var mpackage = mgroup.mpackage
+                       if not mpackage.accept(path) then
+                               mgroup = null
+                               toolcontext.info("module `{path}` excluded from package `{mpackage}`", 2)
+                       end
+               end
                if mgroup == null then
                        # singleton package
                        var loc = new Location.opaque_file(path)
@@ -473,6 +480,13 @@ redef class ModelBuilder
                        if not stopper.file_exists then
                                # Recursively get the parent group
                                parent = identify_group(parentpath)
+                               if parent != null then do
+                                       var mpackage = parent.mpackage
+                                       if not mpackage.accept(dirpath) then
+                                               toolcontext.info("directory `{dirpath}` excluded from package `{mpackage}`", 2)
+                                               parent = null
+                                       end
+                               end
                                if parent == null then
                                        # Parent is not a group, thus we are not a group either
                                        mgroups[rdp] = null
@@ -1064,6 +1078,27 @@ redef class MPackage
        #
        # Some packages, like stand-alone packages or virtual packages have no `ini` file associated.
        var ini: nullable ConfigTree = null
+
+       # Array of relative source paths excluded according to the `source.exclude` key of the `ini`
+       var excludes: nullable Array[String] is lazy do
+               var ini = self.ini
+               if ini == null then return null
+               var exclude = ini["source.exclude"]
+               if exclude == null then return null
+               var excludes = exclude.split(":")
+               return excludes
+       end
+
+       # Does the source inclusion/inclusion rules of the package `ini` accept such path?
+       fun accept(filepath: String): Bool
+       do
+               var excludes = self.excludes
+               if excludes != null then
+                       var relpath = root.filepath.relpath(filepath)
+                       if excludes.has(relpath) then return false
+               end
+               return true
+       end
 end
 
 redef class MGroup
index 85bb115..5cc235f 100644 (file)
@@ -348,6 +348,12 @@ class ModelBuilder
                var name = qid.n_id.text
                var qname = qid.full_name
 
+               if bad_class_names[mmodule].has(qname) then
+                       error(qid, "Error: class `{qname}` not found in module `{mmodule}`.")
+                       return
+               end
+               bad_class_names[mmodule].add(qname)
+
                var all_classes = model.get_mclasses_by_name(name)
                var hints = new Array[String]
 
@@ -404,6 +410,10 @@ class ModelBuilder
                error(qid, "Error: class `{qname}` not found in module `{mmodule}`.")
        end
 
+       # List of already reported bad class names.
+       # Used to not perform and repeat hints again and again.
+       private var bad_class_names = new MultiHashMap[MModule, String]
+
        # Return the static type associated to the node `ntype`.
        # `mmodule` and `mclassdef` is the context where the call is made (used to understand formal types)
        # In case of problem, an error is displayed on `ntype` and null is returned.
index f875083..4321214 100644 (file)
@@ -178,7 +178,10 @@ if opt_tree.value then
                var pa = mp.mgroup
                while pa != null and not pa.is_interesting do pa = pa.parent
                ot.add(pa, mp)
-               if pa != null then mgroups.add pa
+               while pa != null do
+                       mgroups.add pa
+                       pa = pa.parent
+               end
        end
        for g in mgroups do
                var pa = g.parent
index 8d79e80..c9aa8ae 100644 (file)
@@ -20,7 +20,7 @@ import testing
 
 var toolcontext = new ToolContext
 
-toolcontext.option_context.add_option(toolcontext.opt_full, toolcontext.opt_output, toolcontext.opt_dir, toolcontext.opt_noact, toolcontext.opt_pattern, toolcontext.opt_file, toolcontext.opt_gen_unit, toolcontext.opt_gen_force, toolcontext.opt_gen_private, toolcontext.opt_gen_show, toolcontext.opt_nitc)
+toolcontext.option_context.add_option(toolcontext.opt_full, toolcontext.opt_output, toolcontext.opt_dir, toolcontext.opt_noact, toolcontext.opt_pattern, toolcontext.opt_file, toolcontext.opt_autosav, toolcontext.opt_gen_unit, toolcontext.opt_gen_force, toolcontext.opt_gen_private, toolcontext.opt_gen_show, toolcontext.opt_nitc)
 toolcontext.tooldescription = "Usage: nitunit [OPTION]... <file.nit>...\nExecutes the unit tests from Nit source files."
 
 toolcontext.process_options(args)
index c18c740..13e3dce 100644 (file)
@@ -43,6 +43,23 @@ private class NitwebPhase
                var model = mainmodule.model
                var modelbuilder = toolcontext.modelbuilder
 
+               # Build catalog
+               var catalog = new Catalog(modelbuilder)
+               for mpackage in model.mpackages do
+                       catalog.deps.add_node(mpackage)
+                       for mgroup in mpackage.mgroups do
+                               for mmodule in mgroup.mmodules do
+                                       for imported in mmodule.in_importation.direct_greaters do
+                                               var ip = imported.mpackage
+                                               if ip == null or ip == mpackage then continue
+                                               catalog.deps.add_edge(mpackage, ip)
+                                       end
+                               end
+                       end
+                       catalog.git_info(mpackage)
+                       catalog.package_page(mpackage)
+               end
+
                # Run the server
                var host = toolcontext.opt_host.value or else "localhost"
                var port = toolcontext.opt_port.value
@@ -50,7 +67,7 @@ private class NitwebPhase
                var app = new App
 
                app.use_before("/*", new RequestClock)
-               app.use("/api", new APIRouter(model, modelbuilder, mainmodule))
+               app.use("/api", new APIRouter(model, modelbuilder, mainmodule, catalog))
                app.use("/doc/:namespace", new DocAction(model, mainmodule, modelbuilder))
                app.use("/*", new StaticHandler(toolcontext.share_dir / "nitweb", "index.html"))
                app.use_after("/*", new ConsoleLog)
@@ -59,6 +76,33 @@ private class NitwebPhase
        end
 end
 
+# Group all api handlers in one router.
+class APIRouter
+       super Router
+
+       # Model to pass to handlers.
+       var model: Model
+
+       # ModelBuilder to pass to handlers.
+       var modelbuilder: ModelBuilder
+
+       # Mainmodule to pass to handlers.
+       var mainmodule: MModule
+
+       # Catalog to pass to handlers.
+       var catalog: Catalog
+
+       init do
+               use("/catalog", new APICatalogRouter(model, mainmodule, catalog))
+               use("/list", new APIList(model, mainmodule))
+               use("/search", new APISearch(model, mainmodule))
+               use("/random", new APIRandom(model, mainmodule))
+               use("/entity/:id", new APIEntity(model, mainmodule))
+               use("/code/:id", new APIEntityCode(model, mainmodule, modelbuilder))
+               use("/uml/:id", new APIEntityUML(model, mainmodule))
+       end
+end
+
 # build toolcontext
 var toolcontext = new ToolContext
 var tpl = new Template
index 292ff28..c7dae85 100644 (file)
@@ -3,6 +3,8 @@ name=nitc
 tags=devel,cli
 maintainer=Jean Privat <jean@pryen.org>
 license=Apache-2.0
+[source]
+exclude=parser/parser_abs.nit:parser/.parser-nofact.nit
 [upstream]
 browse=https://github.com/nitlang/nit/tree/master/src
 git=https://github.com/nitlang/nit.git
index b01d7dd..36ce1f9 100644 (file)
@@ -198,6 +198,9 @@ abstract class UnitTest
        # The location where the error occurred, if it makes sense.
        var error_location: nullable Location = null is writable
 
+       # Additional noteworthy information when a test success.
+       var info: nullable String = null
+
        # A colorful `[OK]` or `[KO]`.
        fun status_tag(color: nullable Bool): String do
                color = color or else true
@@ -236,6 +239,10 @@ abstract class UnitTest
                else
                        res = "{status_tag(color)} {full_name}"
                        if more_message != null then res += more_message
+                       var info = self.info
+                       if info != null then
+                               res += "\n     {info}"
+                       end
                end
                return res
        end
index 309393c..a37d243 100644 (file)
@@ -24,6 +24,8 @@ redef class ToolContext
        var opt_file = new OptionString("Specify test suite location", "-t", "--target-file")
        # --pattern
        var opt_pattern = new OptionString("Only run test case with name that match pattern", "-p", "--pattern")
+       # --autosav
+       var opt_autosav = new OptionBool("Automatically create/update .res files for black box testing", "--autosav")
 end
 
 # Used to test nitunit test files.
@@ -269,7 +271,8 @@ class TestCase
                var test_file = test_suite.test_file
                var res_name = "{test_file}_{method_name.escape_to_c}"
                var res = toolcontext.safe_exec("{test_file}.bin {method_name} > '{res_name}.out1' 2>&1 </dev/null")
-               self.raw_output = "{res_name}.out1".to_path.read_all
+               var raw_output = "{res_name}.out1".to_path.read_all
+               self.raw_output = raw_output
                # set test case result
                if res != 0 then
                        error = "Runtime Error in file {test_file}.nit"
@@ -279,17 +282,36 @@ class TestCase
                        var mmodule = test_method.mclassdef.mmodule
                        var file = mmodule.filepath
                        if file != null then
-                               var sav = file.dirname / mmodule.name + ".sav" / test_method.name + ".res"
-                               if sav.file_exists then
+                               var tries = [ file.dirname / mmodule.name + ".sav" / test_method.name + ".res",
+                                       file.dirname / "sav" / test_method.name + ".res" ,
+                                       file.dirname / test_method.name + ".res" ]
+                               var savs = [ for t in tries do if t.file_exists then t ]
+                               if savs.length == 1 then
+                                       var sav = savs.first
                                        toolcontext.info("Diff output with {sav}", 1)
                                        res = toolcontext.safe_exec("diff -u --label 'expected:{sav}' --label 'got:{res_name}.out1' '{sav}' '{res_name}.out1' > '{res_name}.diff' 2>&1 </dev/null")
-                                       if res != 0 then
+                                       if res == 0 then
+                                               # OK
+                                       else if toolcontext.opt_autosav.value then
+                                               raw_output.write_to_file(sav)
+                                               info = "Expected output updated: {sav} (--autoupdate)"
+                                       else
                                                self.raw_output = "Diff\n" + "{res_name}.diff".to_path.read_all
                                                error = "Difference with expected output: diff -u {sav} {res_name}.out1"
                                                toolcontext.modelbuilder.failed_tests += 1
                                        end
-                               else
-                                       toolcontext.info("No diff: {sav} not found", 2)
+                               else if savs.length > 1 then
+                                       toolcontext.info("Conflicting diffs: {savs.join(", ")}", 1)
+                                       error = "Conflicting expected output: {savs.join(", ", " and ")} all exist"
+                                       toolcontext.modelbuilder.failed_tests += 1
+                               else if not raw_output.is_empty then
+                                       toolcontext.info("No diff: {tries.join(", ", " or ")} not found", 1)
+                                       if toolcontext.opt_autosav.value then
+                                               var sav = tries.first
+                                               sav.dirname.mkdir
+                                               raw_output.write_to_file(sav)
+                                               info = "Expected output saved: {sav} (--autoupdate)"
+                                       end
                                end
                        end
                end
diff --git a/src/web/api_catalog.nit b/src/web/api_catalog.nit
new file mode 100644 (file)
index 0000000..72d207f
--- /dev/null
@@ -0,0 +1,138 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module api_catalog
+
+import web_base
+import catalog
+
+# Group all api handlers in one router.
+class APICatalogRouter
+       super Router
+
+       # Model to pass to handlers.
+       var model: Model
+
+       # Mainmodule to pass to handlers.
+       var mainmodule: MModule
+
+       # Catalog to pass to handlers.
+       var catalog: Catalog
+
+       init do
+               use("/highlighted", new APICatalogHighLighted(model, mainmodule, catalog))
+               use("/required", new APICatalogMostRequired(model, mainmodule, catalog))
+               use("/bytags", new APICatalogByTags(model, mainmodule, catalog))
+               use("/contributors", new APICatalogContributors(model, mainmodule, catalog))
+               use("/stats", new APICatalogStats(model, mainmodule, catalog))
+       end
+end
+
+abstract class APICatalogHandler
+       super APIHandler
+
+       var catalog: Catalog
+
+       # List the 10 best packages from `cpt`
+       fun list_best(cpt: Counter[MPackage]): JsonArray do
+               var res = new JsonArray
+               var best = cpt.sort
+               for i in [1..10] do
+                       if i > best.length then break
+                       res.add best[best.length-i]
+               end
+               return res
+       end
+
+       # List packages by group.
+       fun list_by(map: MultiHashMap[Object, MPackage]): JsonObject do
+               var res = new JsonObject
+               var keys = map.keys.to_a
+               alpha_comparator.sort(keys)
+               for k in keys do
+                       var projs = map[k].to_a
+                       alpha_comparator.sort(projs)
+                       res[k.to_s.html_escape] = new JsonArray.from(projs)
+               end
+               return res
+       end
+end
+
+class APICatalogStats
+       super APICatalogHandler
+
+       redef fun get(req, res) do
+               var obj = new JsonObject
+               obj["packages"] = model.mpackages.length
+               obj["maintainers"] = catalog.maint2proj.length
+               obj["contributors"] = catalog.contrib2proj.length
+               obj["modules"] = catalog.mmodules.sum
+               obj["classes"] = catalog.mclasses.sum
+               obj["methods"] = catalog.mmethods.sum
+               obj["loc"] = catalog.loc.sum
+               res.json obj
+       end
+end
+
+class APICatalogHighLighted
+       super APICatalogHandler
+
+       redef fun get(req, res) do res.json list_best(catalog.score)
+end
+
+class APICatalogMostRequired
+       super APICatalogHandler
+
+       redef fun get(req, res) do
+               if catalog.deps.not_empty then
+                       var reqs = new Counter[MPackage]
+                       for p in model.mpackages do
+                               reqs[p] = catalog.deps[p].smallers.length - 1
+                       end
+                       res.json list_best(reqs)
+                       return
+               end
+               res.json new JsonArray
+       end
+end
+
+class APICatalogByTags
+       super APICatalogHandler
+
+       redef fun get(req, res) do res.json list_by(catalog.tag2proj)
+end
+
+class APICatalogContributors
+       super APICatalogHandler
+
+       redef fun get(req, res) do
+               var obj = new JsonObject
+               obj["maintainers"] = new JsonArray.from(catalog.maint2proj.keys)
+               obj["contributors"] = new JsonArray.from(catalog.contrib2proj.keys)
+               res.json obj
+       end
+end
+
+redef class Person
+       super Jsonable
+
+       redef fun to_json do
+               var obj = new JsonObject
+               obj["name"] = name
+               obj["email"] = email
+               obj["page"] = page
+               obj["hash"] = (email or else "").md5.to_lower
+               return obj.to_json
+       end
+end
index 9bbe5ad..080dcb2 100644 (file)
@@ -18,65 +18,6 @@ import web_base
 import highlight
 import uml
 
-# Specific handler for nitweb API.
-abstract class APIHandler
-       super ModelHandler
-
-       # The JSON API does not filter anything by default.
-       #
-       # So we can cache the model view.
-       var view: ModelView is lazy do
-               var view = new ModelView(model)
-               view.min_visibility = private_visibility
-               view.include_fictive = true
-               view.include_empty_doc = true
-               view.include_attribute = true
-               view.include_test_suite = true
-               return view
-       end
-
-       # Try to load the mentity from uri with `/:id`.
-       #
-       # Send 400 if `:id` is null.
-       # Send 404 if no entity is found.
-       # Return null in both cases.
-       fun mentity_from_uri(req: HttpRequest, res: HttpResponse): nullable MEntity do
-               var id = req.param("id")
-               if id == null then
-                       res.error 400
-                       return null
-               end
-               var mentity = find_mentity(view, id)
-               if mentity == null then
-                       res.error 404
-               end
-               return mentity
-       end
-end
-
-# Group all api handlers in one router.
-class APIRouter
-       super Router
-
-       # Model to pass to handlers.
-       var model: Model
-
-       # ModelBuilder to pass to handlers.
-       var modelbuilder: ModelBuilder
-
-       # Mainmodule to pass to handlers.
-       var mainmodule: MModule
-
-       init do
-               use("/list", new APIList(model, mainmodule))
-               use("/search", new APISearch(model, mainmodule))
-               use("/random", new APIRandom(model, mainmodule))
-               use("/entity/:id", new APIEntity(model, mainmodule))
-               use("/code/:id", new APIEntityCode(model, mainmodule, modelbuilder))
-               use("/uml/:id", new APIEntityUML(model, mainmodule))
-       end
-end
-
 # List all mentities.
 #
 # MEntities can be filtered on their kind using the `k` parameter.
index c6a12f0..2a22001 100644 (file)
@@ -17,3 +17,4 @@ module web
 
 import web_actions
 import model_api
+import api_catalog
index ec9bf75..fcaac53 100644 (file)
@@ -51,6 +51,42 @@ class ModelHandler
        end
 end
 
+# Specific handler for nitweb API.
+abstract class APIHandler
+       super ModelHandler
+
+       # The JSON API does not filter anything by default.
+       #
+       # So we can cache the model view.
+       var view: ModelView is lazy do
+               var view = new ModelView(model)
+               view.min_visibility = private_visibility
+               view.include_fictive = true
+               view.include_empty_doc = true
+               view.include_attribute = true
+               view.include_test_suite = true
+               return view
+       end
+
+       # Try to load the mentity from uri with `/:id`.
+       #
+       # Send 400 if `:id` is null.
+       # Send 404 if no entity is found.
+       # Return null in both cases.
+       fun mentity_from_uri(req: HttpRequest, res: HttpResponse): nullable MEntity do
+               var id = req.param("id")
+               if id == null then
+                       res.error 400
+                       return null
+               end
+               var mentity = find_mentity(view, id)
+               if mentity == null then
+                       res.error 404
+               end
+               return mentity
+       end
+end
+
 # A NitView is rendered by an action.
 interface NitView
        # Renders this view and returns something that can be written to a HTTP response.
index 568f76a..5f5a31a 100644 (file)
@@ -3,4 +3,6 @@ base_simple3.nit project1
 -t -r base_simple3.nit project1
 -s base_simple3.nit project1
 -M base_simple3.nit base_simple_import.nit
--td project1/module3.nit
+-td project1/module3.nit project1/subdir/subdir2/subdir3/submodule.nit
+test_prog --no-color
+test_prog/game/excluded.nit test_prog/game/excluded_dir/more.nit -t --no-color
index 396ac26..1ef81d1 100644 (file)
@@ -1,3 +1,4 @@
 --no-color -W test_advice_repeated_types.nit
 --no-color base_simple3.nit; echo $?
 --no-color error_mod_unk.nit; echo $?
+--no-color test_prog
diff --git a/tests/project1/subdir/subdir2/subdir3/submodule.nit b/tests/project1/subdir/subdir2/subdir3/submodule.nit
new file mode 100644 (file)
index 0000000..3355283
--- /dev/null
@@ -0,0 +1 @@
+import module0
diff --git a/tests/project1/uselessdir/uselessdir2/uselessfile b/tests/project1/uselessdir/uselessdir2/uselessfile
new file mode 100644 (file)
index 0000000..e69de29
index 01782ba..9238b51 100644 (file)
@@ -1,8 +1,35 @@
+Empty README for group `excluded` (readme-warning)
+Errors: 0. Warnings: 1.
+MGroupPage excluded
+       # excluded.section
+               ## excluded.intro
+               ## excluded.concerns
+               ## excluded.concern
+               ## excluded.concern
+               ## excluded-.concern
+                       ### excluded-.definition
+                               #### excluded-.intros_redefs
+                                       ##### list.group
+                                               ###### excluded-.intros
+                                               ###### excluded-.redefs
+
+MModulePage excluded
+       # excluded.section
+               ## excluded-.intro
+               ## excluded-.importation
+                       ### excluded-.graph
+                       ### list.group
+                               #### excluded-.imports
+                               #### excluded-.clients
+
 OverviewPage Overview
        # home.article
                ## packages.section
+                       ### excluded.definition
                        ### test_prog.definition
 
+ReadmePage excluded
+
 ReadmePage test_prog
        # mdarticle-0
 
@@ -1007,24 +1034,24 @@ MModulePage rpg
                                #### test_prog__rpg__rpg.imports
                                #### test_prog__rpg__rpg.clients
 
-Generated 96 pages
+Generated 99 pages
  list:
-  MPropertyPage: 58 (60.41%)
-  MClassPage: 20 (20.83%)
-  MModulePage: 8 (8.33%)
-  MGroupPage: 4 (4.16%)
-  ReadmePage: 4 (4.16%)
-  SearchPage: 1 (1.04%)
-  OverviewPage: 1 (1.04%)
-Found 182 mentities
+  MPropertyPage: 58 (58.58%)
+  MClassPage: 20 (20.20%)
+  MModulePage: 9 (9.09%)
+  ReadmePage: 5 (5.05%)
+  MGroupPage: 5 (5.05%)
+  SearchPage: 1 (1.01%)
+  OverviewPage: 1 (1.01%)
+Found 185 mentities
  list:
-  MMethodDef: 68 (37.36%)
-  MMethod: 57 (31.31%)
-  MClassDef: 22 (12.08%)
-  MClass: 20 (10.98%)
-  MModule: 8 (4.39%)
-  MGroup: 4 (2.19%)
+  MMethodDef: 68 (36.75%)
+  MMethod: 57 (30.81%)
+  MClassDef: 22 (11.89%)
+  MClass: 20 (10.81%)
+  MModule: 9 (4.86%)
+  MGroup: 5 (2.70%)
+  MPackage: 2 (1.08%)
   MVirtualTypeDef: 1 (0.54%)
   MVirtualTypeProp: 1 (0.54%)
-  MPackage: 1 (0.54%)
 quicksearch-list.js
index eca970e..2e6f89d 100644 (file)
@@ -9,4 +9,7 @@ project1 (\e[33mproject1\e[m)
 |--\e[1mproject1\e[m (\e[33mproject1/project1.nit\e[m)
 `--subdir (\e[33mproject1/subdir\e[m)
    |--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)
-   `--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
+   |--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
+   `--subdir2 (\e[33mproject1/subdir/subdir2\e[m)
+      `--subdir3 (\e[33mproject1/subdir/subdir2/subdir3\e[m)
+         `--\e[1msubmodule\e[m (\e[33mproject1/subdir/subdir2/subdir3/submodule.nit\e[m)
index eca970e..2e6f89d 100644 (file)
@@ -9,4 +9,7 @@ project1 (\e[33mproject1\e[m)
 |--\e[1mproject1\e[m (\e[33mproject1/project1.nit\e[m)
 `--subdir (\e[33mproject1/subdir\e[m)
    |--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)
-   `--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
+   |--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
+   `--subdir2 (\e[33mproject1/subdir/subdir2\e[m)
+      `--subdir3 (\e[33mproject1/subdir/subdir2/subdir3\e[m)
+         `--\e[1msubmodule\e[m (\e[33mproject1/subdir/subdir2/subdir3/submodule.nit\e[m)
index c5ec486..ed5df5e 100644 (file)
@@ -9,7 +9,10 @@ project1 (\e[33mproject1\e[m)
 |--\e[1mproject1\e[m (\e[33mproject1/project1.nit\e[m)
 `--subdir (\e[33mproject1/subdir\e[m)
    |--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)
-   `--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
+   |--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
+   `--subdir2 (\e[33mproject1/subdir/subdir2\e[m)
+      `--subdir3 (\e[33mproject1/subdir/subdir2/subdir3\e[m)
+         `--\e[1msubmodule\e[m (\e[33mproject1/subdir/subdir2/subdir3/submodule.nit\e[m)
 project2 (\e[33mproject1/project2\e[m)
 |--\e[1mfoo\e[m (\e[33mproject1/project2/foo.nit\e[m)
 `--\e[1mproject2\e[m (\e[33mproject1/project2/project2.nit\e[m)
index 56570ed..64895a3 100644 (file)
@@ -8,3 +8,4 @@ project1>subdir>\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
 project1>\e[1mmodule_01\e[m (\e[33mproject1/module_01.nit\e[m)
 project1>\e[1mmodule_02\e[m (\e[33mproject1/module_02.nit\e[m)
 project1>\e[1mproject1\e[m (\e[33mproject1/project1.nit\e[m)
+project1>subdir>subdir2>subdir3>\e[1msubmodule\e[m (\e[33mproject1/subdir/subdir2/subdir3/submodule.nit\e[m)
index 8ebf96a..7cf6fd3 100644 (file)
@@ -2,4 +2,7 @@ project1 (\e[33mproject1\e[m)
 |--\e[1mmodule1\e[m (\e[33mproject1/module1.nit\e[m)
 |--\e[1mmodule3\e[m (\e[33mproject1/module3.nit\e[m)\e[37m (module4)\e[m
 `--subdir (\e[33mproject1/subdir\e[m)
-   `--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)\e[37m (module1)\e[m
+   |--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)\e[37m (module1)\e[m
+   `--subdir2 (\e[33mproject1/subdir/subdir2\e[m)
+      `--subdir3 (\e[33mproject1/subdir/subdir2/subdir3\e[m)
+         `--\e[1msubmodule\e[m (\e[33mproject1/subdir/subdir2/subdir3/submodule.nit\e[m)
diff --git a/tests/sav/nitls_args7.res b/tests/sav/nitls_args7.res
new file mode 100644 (file)
index 0000000..a0a686e
--- /dev/null
@@ -0,0 +1,12 @@
+test_prog: Test program for model tools. (test_prog)
+|--game: Gaming group (test_prog/game)
+|  `--\e[1mgame\e[m: A game abstraction for RPG. (test_prog/game/game.nit)
+|--platform: Fictive Crappy Platform. (test_prog/platform)
+|  `--\e[1mplatform\e[m: Declares base types allowed on the platform. (test_prog/platform/platform.nit)
+|--rpg: Role Playing Game group (test_prog/rpg)
+|  |--\e[1mcareers\e[m: Careers of the game. (test_prog/rpg/careers.nit)
+|  |--\e[1mcharacter\e[m: Characters are playable entity in the world. (test_prog/rpg/character.nit)
+|  |--\e[1mcombat\e[m: COmbat interactions between characters. (test_prog/rpg/combat.nit)
+|  |--\e[1mraces\e[m: Races of the game. (test_prog/rpg/races.nit)
+|  `--\e[1mrpg\e[m: A worlg RPG abstraction. (test_prog/rpg/rpg.nit)
+`--\e[1mtest_prog\e[m: A test program with a fake model to check model tools. (test_prog/test_prog.nit)
diff --git a/tests/sav/nitls_args8.res b/tests/sav/nitls_args8.res
new file mode 100644 (file)
index 0000000..b80e7ce
--- /dev/null
@@ -0,0 +1,2 @@
+\e[1mexcluded\e[m (test_prog/game/excluded.nit)
+\e[1mmore\e[m (test_prog/game/excluded_dir/more.nit)
index 7d1f2bd..773614a 100644 (file)
@@ -1,4 +1,4 @@
-==== Test-suite of module test_nitunit4::test_nitunit4 | tests: 3
+==== Test-suite of module test_nitunit4::test_nitunit4 | tests: 4
 [KO] test_nitunit4$TestTestSuite$test_foo
      test_nitunit4/test_nitunit4.nit:22,2--26,4: Runtime Error in file nitunit.out/gen_test_nitunit4.nit
      Output
@@ -9,10 +9,10 @@
 
 [OK] test_nitunit4$TestTestSuite$test_bar
 [KO] test_nitunit4$TestTestSuite$test_baz
-     test_nitunit4/test_nitunit4.nit:32,2--34,4: Difference with expected output: diff -u test_nitunit4/test_nitunit4.sav/test_baz.res nitunit.out/gen_test_nitunit4_test_baz.out1
+     test_nitunit4/test_nitunit4.nit:32,2--34,4: Difference with expected output: diff -u test_nitunit4/test_baz.res nitunit.out/gen_test_nitunit4_test_baz.out1
      Output
        Diff
-       --- expected:test_nitunit4/test_nitunit4.sav/test_baz.res
+       --- expected:test_nitunit4/test_baz.res
        +++ got:nitunit.out/gen_test_nitunit4_test_baz.out1
        @@ -1 +1,3 @@
        -Bad result file
        +Tested method
        +After Test
 
+[KO] test_nitunit4$TestTestSuite$test_sav_conflict
+     test_nitunit4/test_nitunit4.nit:36,2--38,4: Conflicting expected output: test_nitunit4/test_nitunit4.sav/test_sav_conflict.res, test_nitunit4/sav/test_sav_conflict.res and test_nitunit4/test_sav_conflict.res all exist
+     Output
+       Before Test
+       Tested method
+       After Test
 
-Docunits: Entities: 12; Documented ones: 0; With nitunits: 0
-Test suites: Classes: 1; Test Cases: 3; Failures: 2
-[FAILURE] 2/3 tests failed.
+
+Docunits: Entities: 13; Documented ones: 0; With nitunits: 0
+Test suites: Classes: 1; Test Cases: 4; Failures: 3
+[FAILURE] 3/4 tests failed.
 `nitunit.out` is not removed for investigation.
 <testsuites><testsuite package="test_nitunit4&gt;"></testsuite><testsuite package="test_nitunit4::nitunit4"></testsuite><testsuite package="test_nitunit4"><testcase classname="nitunit.test_nitunit4::test_nitunit4.test_nitunit4::TestTestSuite" name="test_nitunit4::TestTestSuite::test_foo"><error>Runtime Error in file nitunit.out&#47;gen_test_nitunit4.nit</error><system-err>Before Test
 Tested method
@@ -32,12 +39,15 @@ Runtime error: Assert failed (test_nitunit4&#47;test_nitunit4_base.nit:31)
 </system-err></testcase><testcase classname="nitunit.test_nitunit4::test_nitunit4.test_nitunit4::TestTestSuite" name="test_nitunit4::TestTestSuite::test_bar"><system-err>Before Test
 Tested method
 After Test
-</system-err></testcase><testcase classname="nitunit.test_nitunit4::test_nitunit4.test_nitunit4::TestTestSuite" name="test_nitunit4::TestTestSuite::test_baz"><error>Difference with expected output: diff -u test_nitunit4&#47;test_nitunit4.sav&#47;test_baz.res nitunit.out&#47;gen_test_nitunit4_test_baz.out1</error><system-err>Diff
---- expected:test_nitunit4&#47;test_nitunit4.sav&#47;test_baz.res
+</system-err></testcase><testcase classname="nitunit.test_nitunit4::test_nitunit4.test_nitunit4::TestTestSuite" name="test_nitunit4::TestTestSuite::test_baz"><error>Difference with expected output: diff -u test_nitunit4&#47;test_baz.res nitunit.out&#47;gen_test_nitunit4_test_baz.out1</error><system-err>Diff
+--- expected:test_nitunit4&#47;test_baz.res
 +++ got:nitunit.out&#47;gen_test_nitunit4_test_baz.out1
 @@ -1 +1,3 @@
 -Bad result file
 +Before Test
 +Tested method
 +After Test
+</system-err></testcase><testcase classname="nitunit.test_nitunit4::test_nitunit4.test_nitunit4::TestTestSuite" name="test_nitunit4::TestTestSuite::test_sav_conflict"><error>Conflicting expected output: test_nitunit4&#47;test_nitunit4.sav&#47;test_sav_conflict.res, test_nitunit4&#47;sav&#47;test_sav_conflict.res and test_nitunit4&#47;test_sav_conflict.res all exist</error><system-err>Before Test
+Tested method
+After Test
 </system-err></testcase></testsuite><testsuite package="test_nitunit4::test_nitunit4"></testsuite><testsuite></testsuite><testsuite package="test_nitunit4::test_nitunit4_base"></testsuite><testsuite></testsuite></testsuites>
\ No newline at end of file
index 25574f7..e0353bf 100644 (file)
@@ -1,13 +1,13 @@
  population: 3
  minimum value: 2
- maximum value: 11
- total value: 16
- average value: 5.33
+ maximum value: 12
+ total value: 22
+ average value: 7.33
  distribution:
-  <=2: sub-population=1 (33.33%); cumulated value=2 (12.50%)
-  <=4: sub-population=1 (33.33%); cumulated value=3 (18.75%)
-  <=16: sub-population=1 (33.33%); cumulated value=11 (68.75%)
+  <=2: sub-population=1 (33.33%); cumulated value=2 (9.09%)
+  <=8: sub-population=1 (33.33%); cumulated value=8 (36.36%)
+  <=16: sub-population=1 (33.33%); cumulated value=12 (54.54%)
  list:
-  nit: 11 (68.75%)
-  : 3 (18.75%)
-  ini: 2 (12.50%)
+  nit: 12 (54.54%)
+  : 8 (36.36%)
+  ini: 2 (9.09%)
index 4ac86da..fdc3be7 100644 (file)
@@ -1,12 +1,12 @@
 # mpackages:
-test_prog
+excluded test_prog
 ------------------------------------
-test_prog
+excluded test_prog
 
 # mmodules:
-careers character combat game platform races rpg test_prog
+careers character combat excluded game platform races rpg test_prog
 ------------------------------------
-careers character combat game platform races rpg test_prog
+careers character combat excluded game platform races rpg test_prog
 
 # mclasses:
 Alcoholic Bool Career Character Combatable Dwarf Elf Float Game Human Int List Magician Object Race Starter String Sys Warrior Weapon
index 01532de..b1fe714 100644 (file)
@@ -2,6 +2,9 @@ test_prog/
 |--test_prog/README.md
 |--test_prog/game
 |  |--test_prog/game/README.md
+|  |--test_prog/game/excluded.nit
+|  |--test_prog/game/excluded_dir
+|  |  `--test_prog/game/excluded_dir/more.nit
 |  `--test_prog/game/game.nit
 |--test_prog/package.ini
 |--test_prog/platform
diff --git a/tests/test_nitunit4/sav/test_sav_conflict.res b/tests/test_nitunit4/sav/test_sav_conflict.res
new file mode 100644 (file)
index 0000000..7b3a785
--- /dev/null
@@ -0,0 +1 @@
+BAD
index cd13aca..8864263 100644 (file)
@@ -32,4 +32,8 @@ class TestTestSuite
        fun test_baz do
                print "Tested method"
        end
+
+       fun test_sav_conflict do
+               print "Tested method"
+       end
 end
diff --git a/tests/test_nitunit4/test_nitunit4.sav/test_sav_conflict.res b/tests/test_nitunit4/test_nitunit4.sav/test_sav_conflict.res
new file mode 100644 (file)
index 0000000..7b3a785
--- /dev/null
@@ -0,0 +1 @@
+BAD
diff --git a/tests/test_nitunit4/test_sav_conflict.res b/tests/test_nitunit4/test_sav_conflict.res
new file mode 100644 (file)
index 0000000..7b3a785
--- /dev/null
@@ -0,0 +1 @@
+BAD
diff --git a/tests/test_prog/game/excluded.nit b/tests/test_prog/game/excluded.nit
new file mode 100644 (file)
index 0000000..d296d4b
--- /dev/null
@@ -0,0 +1 @@
+This is not a valid Nit program
diff --git a/tests/test_prog/game/excluded_dir/more.nit b/tests/test_prog/game/excluded_dir/more.nit
new file mode 100644 (file)
index 0000000..d296d4b
--- /dev/null
@@ -0,0 +1 @@
+This is not a valid Nit program
index ff4b70d..bfcd472 100644 (file)
@@ -5,6 +5,8 @@ tags=test,game
 maintainer=John Doe <jdoe@example.com> (http://www.example.com/~jdoe), Spider-Man
 more_contributors=Riri <riri@example.com>, Fifi (http://www.example.com/~fifi), Loulou
 license=Apache-2.0
+[source]
+exclude=game/excluded.nit:game/excluded_dir
 [upstream]
 browse=https://github.com/nitlang/nit/tree/master/tests/test_prog
 git=https://github.com/nitlang/nit.git