learn-lab 0.1.0.rc1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f02fc168601cfde48fab19367a3a9fcb2fa5a88f0c010e507b4c71329e06ecc2
4
+ data.tar.gz: d3e0e54341575734de538394c133a1b40952cf33dfb9481065a03d518e6e6650
5
+ SHA512:
6
+ metadata.gz: fda10060c1877e47e94b7d933b5adff5ea921e564732f93631d13d2c05cd06585034f4915df81573607734d9b043b5872da879baf126210386d17bbf8c101b7b
7
+ data.tar.gz: 0a8df382daa76d1b2ce64e134c30fec8d833620701783be28fdee2224448b98889a70387cf8605a755a2f6a9832e9fcc8944fdbde8ffe05775c08b1db3611e77
@@ -0,0 +1,48 @@
1
+ version: 2.1
2
+ jobs:
3
+ build:
4
+ parallelism: 1
5
+ docker:
6
+ - image: circleci/ruby:2.6.5-node-browsers
7
+ working_directory: ~/repo
8
+ steps:
9
+ - checkout
10
+ - restore_cache:
11
+ keys:
12
+ - v1-dependencies-{{ checksum "learn-lab.gemspec" }}
13
+ - v1-dependencies-
14
+ - run:
15
+ name: Install Dependencies
16
+ command: |
17
+ ./bin/setup circleci
18
+ - save_cache:
19
+ paths:
20
+ - ./vendor/bundle
21
+ key: v1-dependencies-{{ checksum "learn-lab.gemspec" }}
22
+ - run:
23
+ name: Run CI Script
24
+ command: |
25
+ ./bin/ci
26
+ - store_test_results:
27
+ path: /tmp/test-results
28
+ - store_artifacts:
29
+ path: /tmp/test-results
30
+ destination: test-results
31
+ workflows:
32
+ build:
33
+ jobs:
34
+ - build
35
+ nightly:
36
+ triggers:
37
+ - schedule:
38
+ cron: "0 0 * * *"
39
+ filters:
40
+ branches:
41
+ only:
42
+ - master
43
+ jobs:
44
+ - build
45
+ experimental:
46
+ notify:
47
+ branches:
48
+ only: master
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/examples.txt
8
+ /spec/fixtures/**/node_modules/
9
+ /spec/reports/
10
+ /test-results/
11
+ /tmp/
12
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --require spec_helper
2
+ --exclude-pattern spec/fixtures/**/*
@@ -0,0 +1,31 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ Exclude:
4
+ - 'Gemfile'
5
+ - 'Rakefile'
6
+ - 'bin/*'
7
+ - 'spec/fixtures/**/*'
8
+ - 'tmp/**/*'
9
+ - 'vendor/bundle/**/*'
10
+ TargetRubyVersion: 2.5
11
+
12
+ Layout/SpaceAroundEqualsInParameterDefault:
13
+ EnforcedStyle: no_space
14
+
15
+ Metrics/BlockLength:
16
+ ExcludedMethods:
17
+ - describe
18
+ - context
19
+ - it
20
+
21
+ Layout/LineLength:
22
+ Max: 89
23
+
24
+ Style/BlockDelimiters:
25
+ EnforcedStyle: braces_for_chaining
26
+
27
+ Style/RaiseArgs:
28
+ EnforcedStyle: compact
29
+
30
+ Style/YodaCondition:
31
+ EnforcedStyle: require_for_equality_operators_only
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+ ### Added
9
+ ### Changed
10
+ ### Removed
11
+ ### Fixed
12
+
13
+ ## [0.1.0.rc1]
14
+ Initial version with support for `config` and `test` commands.
15
+
16
+ [Unreleased]: https://github.com/flatiron-labs/learn-lab/compare/v0.1.0.rc1...HEAD
17
+ [0.1.0]: https://github.com/flatiron-labs/learn-lab/releases/tag/v0.1.0.rc1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2020 Flatiron School
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,72 @@
1
+ # Learn Lab
2
+
3
+ The command line interface for Flatiron School enterprise.
4
+
5
+ ## Installation
6
+
7
+ To install the gem, run:
8
+
9
+ ```
10
+ $ gem install learn-lab
11
+ ```
12
+
13
+ then run:
14
+
15
+ ```
16
+ $ learn-lab config
17
+ ```
18
+
19
+ to configure your settings.
20
+
21
+ ## Usage
22
+
23
+ From within a lesson directory, run:
24
+
25
+ ```
26
+ $ learn-lab [command]
27
+ ```
28
+
29
+ To see a list of available commands, run:
30
+
31
+ ```
32
+ $ learn-lab help
33
+ ```
34
+
35
+ ## Development
36
+
37
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
38
+
39
+ To install this gem onto your local machine, run `bundle exec rake install`.
40
+
41
+ This project uses RSpec for tests, and Rubocop for linting code. The default
42
+ Bundler generated scripts provided for both are in the `bin` folder. CI is
43
+ done through
44
+ [CircleCI](https://app.circleci.com/pipelines/github/flatiron-labs/learn-lab). All pushes to any branch will trigger a build that runs specs and lints the code.
45
+
46
+ ### Cutting a New Release
47
+
48
+ Steps for releasing a new version:
49
+
50
+ * update the version number in `version.rb`
51
+ * update the CHANGELOG
52
+ - create a new release heading with the new version number and date
53
+ - create an empty [Unreleased] section with empty headings
54
+ - update links at the bottom to reflect new version
55
+ * commit the changes with a message like: "Prepping for release x.x.x"
56
+ * run `bundle exec rake release` -- this will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org)
57
+ * post a message in the `#engineering-all-staff` Slack channel announcing the new release
58
+
59
+ ## Contributing
60
+
61
+ Bug reports and pull requests are welcome on
62
+ [GitHub](https://github.com/flatiron-labs/learn-lab/issues). To submit a pull request:
63
+
64
+ 1. [Fork](https://github.com/flatiron-labs/learn-lab/fork) the repo
65
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
66
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
67
+ 4. Push to the branch (`git push origin my-new-feature`)
68
+ 5. Create a new Pull Request
69
+
70
+ ## License
71
+
72
+ Licensed under the [MIT License](https://github.com/flatiron-labs/learn-lab/blob/master/LICENSE.txt).
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: %i[spec lint]
9
+
10
+ desc 'Lint code with Rubocop'
11
+ task :lint do
12
+ exec('./bin/rubocop .')
13
+ end
data/bin/ci ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ echo "== Running Specs =="
6
+ ./bin/rspec --format RspecJunitFormatter --out test-results/rspec/results.xml --format progress
7
+
8
+ echo "== Linting Code =="
9
+ ./bin/rubocop
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "learn_lab"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rspec-core", "rspec")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rubocop' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rubocop", "rubocop")
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ if [ "${1-}" = "circleci" ]; then
6
+ echo "== Installing Bundler =="
7
+ gem install bundler:2.1.4
8
+ echo "== Running Bundle Install =="
9
+ bundle install --jobs=4 --retry=3 --path ./vendor/bundle && bundle clean
10
+ else
11
+ echo "== Running Bundle Install =="
12
+ bundle install
13
+ fi
14
+
15
+ echo "== Running NPM Install for Spec Fixtures =="
16
+ npm install --prefix spec/fixtures/mocha/mocha-app/
17
+ npm install --prefix spec/fixtures/mocha/mocha-with-failures/
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'learn_lab'
5
+
6
+ LearnLab::CLI.start(ARGV)
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/learn_lab/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'learn-lab'
7
+ spec.version = LearnLab::VERSION
8
+ spec.authors = ['Flatiron School']
9
+ spec.email = ['learn@flatironschool.com']
10
+
11
+ spec.summary = 'The command line interface for Flatiron School enterprise.'
12
+ spec.homepage = 'https://github.com/flatiron-labs/learn-lab'
13
+ spec.license = 'MIT'
14
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
15
+
16
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org/'
17
+ spec.metadata['changelog_uri'] = 'https://github.com/flatiron-labs/learn-lab/blob/master/CHANGELOG.md'
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = spec.homepage
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+
27
+ spec.bindir = 'exe'
28
+ spec.executables = ['learn-lab']
29
+ spec.require_paths = ['lib']
30
+
31
+ spec.add_development_dependency 'aruba', '~> 1.0'
32
+ spec.add_development_dependency 'rake', '~> 13.0'
33
+ spec.add_development_dependency 'rspec', '~> 3.9'
34
+ spec.add_development_dependency 'rspec_junit_formatter', '~> 0.4'
35
+ spec.add_development_dependency 'rubocop', '~> 1.0'
36
+
37
+ spec.add_runtime_dependency 'git', '~> 1.7'
38
+ spec.add_runtime_dependency 'thor', '~> 1.0'
39
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'json'
5
+ require 'git'
6
+ require 'thor'
7
+ require 'yaml'
8
+
9
+ require 'learn_lab/configuration'
10
+ require 'learn_lab/errors'
11
+ require 'learn_lab/file_system'
12
+ require 'learn_lab/prompt'
13
+ require 'learn_lab/test/runner'
14
+ require 'learn_lab/test/parsers/mocha'
15
+ require 'learn_lab/test/parsers/null'
16
+ require 'learn_lab/test/parsers/rspec'
17
+ require 'learn_lab/test/strategy'
18
+ require 'learn_lab/test/strategies/mocha'
19
+ require 'learn_lab/test/strategies/none'
20
+ require 'learn_lab/test/strategies/rspec'
21
+ require 'learn_lab/token'
22
+ require 'learn_lab/vcs'
23
+ require 'learn_lab/version'
24
+
25
+ require 'learn_lab/cli'
26
+
27
+ # Top-level module
28
+ #
29
+ module LearnLab
30
+ def self.file_system
31
+ FileSystem.new
32
+ end
33
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ # Command-line interface.
5
+ #
6
+ class CLI < Thor
7
+ desc 'test', 'Run tests and get a token'
8
+ long_desc <<-LONG_DESC
9
+ `learn-lab test` will run your tests and output a token that can be
10
+ copy/pasted into Canvas.
11
+ LONG_DESC
12
+ # rubocop:disable Metrics/AbcSize
13
+ # rubocop:disable Metrics/MethodLength
14
+ def test
15
+ configuration = Configuration.open
16
+ unless configuration.valid?
17
+ puts 'Please run `learn-lab config` first'
18
+ exit 0
19
+ end
20
+ repo = VCS.new(LearnLab.file_system.pwd)
21
+ results = LearnLab::Test::Runner.new(repo).run
22
+ exit 0 unless results[:failure_count].zero?
23
+ token = Token.new(configuration.email, repo.remote_url)
24
+ puts "Your token is:\n\n"
25
+ puts chunk(token.encoded, 30)
26
+ end
27
+ # rubocop:enable Metrics/AbcSize
28
+ # rubocop:enable Metrics/MethodLength
29
+
30
+ desc 'config', 'Configure gem'
31
+ long_desc <<-LONG_DESC
32
+ `learn-lab config` will configure this gem. It will prompt you for any
33
+ information it needs and save the configuration to a file in your $HOME directory.
34
+ LONG_DESC
35
+ def config
36
+ configuration = Configuration.open
37
+ email = configuration.email || '<not set>'
38
+
39
+ question = "Enter the email you use to log into Canvas\n" \
40
+ "(your current email is '#{email}'):"
41
+
42
+ prompt = Prompt.new
43
+
44
+ # TODO: What kind of email validation do we want here?
45
+ configuration.email = prompt.ask(question) { |value| !value.empty? }
46
+
47
+ configuration.save!
48
+ puts 'Configuration saved!'
49
+ end
50
+
51
+ private
52
+
53
+ def chunk(string, size)
54
+ string.scan(/.{1,#{size}}/)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ # Interface for dealing with persisted configuration file in YAML format.
5
+ #
6
+ class Configuration
7
+ attr_reader :path, :fs
8
+ attr_accessor :email
9
+
10
+ def self.open(filename='enterprise.yml', **options)
11
+ fs = options.fetch(:fs) { LearnLab.file_system }
12
+ path = fs.join(fs.home, '.flatiron-school', filename)
13
+ data = fs.read_yaml_file(path)
14
+ new(path, data)
15
+ rescue ConfigurationError
16
+ path = fs.join(fs.home, '.flatiron-school', 'enterprise.yml')
17
+ new(path, {})
18
+ end
19
+
20
+ def initialize(path, data, **options)
21
+ @path = path
22
+ @fs = options.fetch(:fs) { LearnLab.file_system }
23
+ @email = data[:email]
24
+ end
25
+
26
+ def save!
27
+ directory = fs.dirname(path)
28
+ fs.mkdir(directory) unless fs.directory?(directory)
29
+ fs.write_yaml_file(path, payload, mode: 'w')
30
+ end
31
+
32
+ def valid?
33
+ !email.nil?
34
+ end
35
+
36
+ private
37
+
38
+ def payload
39
+ { 'email' => email }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ class Error < StandardError; end
5
+ class ConfigurationError < Error; end
6
+ class MissingDependencyError < Error; end
7
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ # Wraps file system related commands.
5
+ #
6
+ class FileSystem
7
+ def read_file(path)
8
+ File.read(path)
9
+ end
10
+
11
+ def read_yaml_file(path, fallback: {})
12
+ data = YAML.load_file(path, fallback: fallback)
13
+ data.transform_keys(&:to_sym)
14
+ rescue Errno::ENOENT, Psych::SyntaxError => e
15
+ raise ConfigurationError.new(e)
16
+ end
17
+
18
+ def read_json_file(path)
19
+ data = File.exist?(path) ? read_file(path) : '{}'
20
+ JSON.parse(data, symbolize_names: true)
21
+ end
22
+
23
+ def home
24
+ Dir.home
25
+ end
26
+
27
+ def pwd
28
+ FileUtils.pwd
29
+ end
30
+
31
+ def join(*args)
32
+ File.join(*args)
33
+ end
34
+
35
+ def dirname(path)
36
+ File.dirname(path)
37
+ end
38
+
39
+ def mkdir(path)
40
+ FileUtils.mkdir_p(path)
41
+ end
42
+
43
+ def directory?(path)
44
+ File.directory?(path)
45
+ end
46
+
47
+ def write_yaml_file(path, data, **options)
48
+ File.write(path, data.to_yaml, **options)
49
+ end
50
+
51
+ def file_exist?(path)
52
+ File.exist?(path)
53
+ end
54
+
55
+ def dir_exist?(path)
56
+ Dir.exist?(path)
57
+ end
58
+
59
+ def dir_entries(path)
60
+ Dir.entries(path)
61
+ end
62
+
63
+ def rm(path)
64
+ FileUtils.rm(path)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ # Special prompt that keeps asking for input until it is valid.
5
+ #
6
+ class Prompt
7
+ attr_reader :output
8
+
9
+ def initialize(output=$stdout)
10
+ @output = output
11
+ end
12
+
13
+ def ask(question, &block)
14
+ input_is_valid = false
15
+ until input_is_valid
16
+ output.print("\n#{question} > ")
17
+ answer = $stdin.gets.chomp
18
+ input_is_valid = block_given? ? block.call(answer) : true
19
+ end
20
+
21
+ answer
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ module Test
5
+ module Parsers
6
+ # Parse Mocha test result output
7
+ #
8
+ class Mocha
9
+ attr_reader :output
10
+
11
+ def initialize(output)
12
+ @output = output
13
+ end
14
+
15
+ def framework
16
+ 'mocha'
17
+ end
18
+
19
+ def duration
20
+ output ? output[:stats][:duration] : 0
21
+ end
22
+
23
+ def example_count
24
+ output ? output[:stats][:tests] : 1
25
+ end
26
+
27
+ def passing_count
28
+ output ? output[:stats][:passes] : 0
29
+ end
30
+
31
+ def pending_count
32
+ output ? output[:stats][:pending] : 0
33
+ end
34
+
35
+ def failure_count
36
+ output ? output[:stats][:failures] : 1
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ module Test
5
+ module Parsers
6
+ # Null parser. Used when no test suite has been detected.
7
+ #
8
+ class Null
9
+ attr_reader :output
10
+
11
+ def framework
12
+ 'none'
13
+ end
14
+
15
+ def duration
16
+ 0
17
+ end
18
+
19
+ def example_count
20
+ 0
21
+ end
22
+
23
+ def passing_count
24
+ 0
25
+ end
26
+
27
+ def pending_count
28
+ 0
29
+ end
30
+
31
+ def failure_count
32
+ 0
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ module Test
5
+ module Parsers
6
+ # Parse RSpec test result output.
7
+ #
8
+ class Rspec
9
+ attr_reader :output
10
+
11
+ def initialize(output)
12
+ @output = output
13
+ end
14
+
15
+ def framework
16
+ 'rspec'
17
+ end
18
+
19
+ def duration
20
+ output ? output[:summary][:duration] : 0
21
+ end
22
+
23
+ def example_count
24
+ output ? output[:summary][:example_count] : 1
25
+ end
26
+
27
+ def passing_count
28
+ example_count - failure_count - pending_count
29
+ end
30
+
31
+ def pending_count
32
+ output ? output[:summary][:pending_count] : 0
33
+ end
34
+
35
+ def failure_count
36
+ output ? output[:summary][:failure_count] : 1
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ module Test
5
+ # Runs a test suite. Lifted from the `learn-test` gem without Ironboard
6
+ # related code.
7
+ #
8
+ class Runner
9
+ attr_reader :repo, :options
10
+
11
+ def initialize(repo, options={})
12
+ @repo = repo
13
+ @options = options
14
+ end
15
+
16
+ def run
17
+ strategy.check_dependencies
18
+ strategy.run
19
+ results = strategy.results
20
+ strategy.cleanup unless options.fetch(:keep, false)
21
+ results
22
+ end
23
+
24
+ def working_directory
25
+ repo.working_directory
26
+ end
27
+
28
+ def strategy
29
+ @strategy ||= begin
30
+ detected = strategies.map { |s| s.new(self) }.detect(&:detect)
31
+ detected || LearnLab::Test::Strategies::None.new(self)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def strategies
38
+ [
39
+ LearnLab::Test::Strategies::Mocha,
40
+ LearnLab::Test::Strategies::Rspec
41
+ ]
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ module Test
5
+ module Strategies
6
+ # Test strategy for Mocha.
7
+ #
8
+ class Mocha < Strategy
9
+ def check_dependencies
10
+ msg = 'Please install NodeJS: https://nodejs.org/en/download'
11
+ raise MissingDependencyError.new(msg) if `which node`.empty?
12
+ end
13
+
14
+ def detect
15
+ js_dependency?(:mocha) || js_dependency?(:'learn-browser')
16
+ end
17
+
18
+ def run
19
+ command = if js_dependency?(:'learn-browser') || js_test_script?
20
+ 'npm test'
21
+ else
22
+ 'node_modules/.bin/mocha -R mocha-multi ' \
23
+ "--reporter-options spec=-,json=#{result_filename}"
24
+ end
25
+ system(command)
26
+ end
27
+
28
+ def parser
29
+ @parser ||= LearnLab::Test::Parsers::Mocha.new(output)
30
+ end
31
+
32
+ private
33
+
34
+ def js_package
35
+ @js_package ||= begin
36
+ path = fs.join(runner.working_directory, 'package.json')
37
+ fs.read_json_file(path)
38
+ end
39
+ end
40
+
41
+ def js_dependency?(dep)
42
+ %i[dependencies devDependencies].any? do |key|
43
+ js_package[key] && js_package[key][dep]
44
+ end
45
+ end
46
+
47
+ def js_test_script?
48
+ (js_package[:scripts] && js_package[:scripts][:test] || '') \
49
+ .include?(result_filename)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ module Test
5
+ module Strategies
6
+ # Null strategy object used when no test suite is detected.
7
+ #
8
+ class None < Strategy
9
+ def run
10
+ out.puts <<~MSG
11
+ This directory doesn't appear to have any specs in it, so
12
+ there’s no test to run.
13
+ MSG
14
+ end
15
+
16
+ def parser
17
+ @parser ||= LearnLab::Test::Parsers::Null.new
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ module Test
5
+ module Strategies
6
+ # Test Strategy for RSpec. Mostly lifted from the `learn-test` gem
7
+ # without Ironboard specific code. Some other refactorings were also
8
+ # made to improve test coverage and fix Rubocop warnings.
9
+ #
10
+ class Rspec < Strategy
11
+ def detect
12
+ spec_dir = fs.join(runner.working_directory, 'spec')
13
+ fs.dir_exist?(spec_dir) && \
14
+ (fs.dir_entries(spec_dir).include?('spec_helper.rb') || \
15
+ fs.dir_entries(spec_dir).include?('rails_helper.rb'))
16
+ end
17
+
18
+ def run
19
+ command_options = [
20
+ '--format documentation',
21
+ '--format j',
22
+ "--out #{result_filename}"
23
+ ]
24
+ system("#{bundle_command}rspec #{command_options.join(' ')}")
25
+ end
26
+
27
+ def parser
28
+ @parser ||= LearnLab::Test::Parsers::Rspec.new(output)
29
+ end
30
+
31
+ private
32
+
33
+ def bundle_command
34
+ bundler? ? 'bundle exec ' : ''
35
+ end
36
+
37
+ def bundler?
38
+ fs.file_exist?('Gemfile') && \
39
+ !!fs.read_file('Gemfile').match(/^\s*gem\s*('|")rspec(-[^'"]+)?('|").*$/)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ module Test
5
+ # Base class for all test tools. Extracted from the `learn-test` gem with
6
+ # some modifications (i.e. this doesn't talk to Ironboard).
7
+ #
8
+ class Strategy
9
+ attr_reader :runner, :options, :out, :fs
10
+
11
+ def initialize(runner, out=$stdout)
12
+ @runner = runner
13
+ @options = runner.options
14
+ @out = out
15
+ @fs = LearnLab.file_system
16
+ end
17
+
18
+ def check_dependencies; end
19
+
20
+ def run
21
+ raise NotImplementedError.new(
22
+ 'you must implement how this strategy runs its tests'
23
+ )
24
+ end
25
+
26
+ def parser
27
+ raise NotImplementedError.new('you must define a parser')
28
+ end
29
+
30
+ # rubocop:disable Metrics/MethodLength
31
+ def results
32
+ {
33
+ repo_name: runner.repo,
34
+ build: {
35
+ test_suite: [{
36
+ framework: parser.framework,
37
+ formatted_output: output,
38
+ duration: parser.duration
39
+ }]
40
+ },
41
+ example_count: parser.example_count,
42
+ passing_count: parser.passing_count,
43
+ pending_count: parser.pending_count,
44
+ failure_count: parser.failure_count
45
+ }
46
+ end
47
+ # rubocop:enable Metrics/MethodLength
48
+
49
+ def output
50
+ return unless result_file?
51
+
52
+ fs.read_json_file(result_file_path)
53
+ end
54
+
55
+ def cleanup
56
+ fs.rm(result_file_path) if result_file?
57
+ end
58
+
59
+ def result_filename
60
+ '.results.json'
61
+ end
62
+
63
+ private
64
+
65
+ def result_file?
66
+ fs.file_exist?(result_file_path)
67
+ end
68
+
69
+ def result_file_path
70
+ fs.join(runner.working_directory, result_filename)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ # Encodable token. Consists of an email, GitHub URL, and timestamp encoded
5
+ # as a Base64 URL safe string.
6
+ #
7
+ class Token
8
+ attr_reader :email, :url, :timestamp
9
+
10
+ def initialize(email, url, timestamp=Time.now.utc)
11
+ @email = email
12
+ @url = url
13
+ @timestamp = timestamp
14
+ end
15
+
16
+ def encoded
17
+ Base64.urlsafe_encode64(payload.to_json)
18
+ end
19
+
20
+ private
21
+
22
+ def payload
23
+ {
24
+ lms_email: email,
25
+ vcs_url: url,
26
+ timestamp: timestamp
27
+ }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ # Adapter for the Version Control System.
5
+ #
6
+ class VCS
7
+ def initialize(directory, &block)
8
+ @repo = Git.open(directory)
9
+ rescue StandardError => e
10
+ msg = "You don't appear to be in a Learn lesson's directory. Please " \
11
+ 'cd to an appropriate directory and try again.'
12
+
13
+ block_given? ? block.call(msg, e) : raise(LearnLab::Error.new(msg))
14
+ end
15
+
16
+ def remote_url
17
+ @repo.remote.url
18
+ end
19
+
20
+ def working_directory
21
+ @repo.dir.path
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LearnLab
4
+ VERSION = '0.1.0.rc1'
5
+ end
metadata ADDED
@@ -0,0 +1,180 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: learn-lab
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.rc1
5
+ platform: ruby
6
+ authors:
7
+ - Flatiron School
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-11-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aruba
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.9'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec_junit_formatter
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: git
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.7'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.7'
97
+ - !ruby/object:Gem::Dependency
98
+ name: thor
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.0'
111
+ description:
112
+ email:
113
+ - learn@flatironschool.com
114
+ executables:
115
+ - learn-lab
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".circleci/config.yml"
120
+ - ".gitignore"
121
+ - ".rspec"
122
+ - ".rubocop.yml"
123
+ - CHANGELOG.md
124
+ - Gemfile
125
+ - LICENSE.txt
126
+ - README.md
127
+ - Rakefile
128
+ - bin/ci
129
+ - bin/console
130
+ - bin/rspec
131
+ - bin/rubocop
132
+ - bin/setup
133
+ - exe/learn-lab
134
+ - learn-lab.gemspec
135
+ - lib/learn_lab.rb
136
+ - lib/learn_lab/cli.rb
137
+ - lib/learn_lab/configuration.rb
138
+ - lib/learn_lab/errors.rb
139
+ - lib/learn_lab/file_system.rb
140
+ - lib/learn_lab/prompt.rb
141
+ - lib/learn_lab/test/parsers/mocha.rb
142
+ - lib/learn_lab/test/parsers/null.rb
143
+ - lib/learn_lab/test/parsers/rspec.rb
144
+ - lib/learn_lab/test/runner.rb
145
+ - lib/learn_lab/test/strategies/mocha.rb
146
+ - lib/learn_lab/test/strategies/none.rb
147
+ - lib/learn_lab/test/strategies/rspec.rb
148
+ - lib/learn_lab/test/strategy.rb
149
+ - lib/learn_lab/token.rb
150
+ - lib/learn_lab/vcs.rb
151
+ - lib/learn_lab/version.rb
152
+ homepage: https://github.com/flatiron-labs/learn-lab
153
+ licenses:
154
+ - MIT
155
+ metadata:
156
+ allowed_push_host: https://rubygems.org/
157
+ changelog_uri: https://github.com/flatiron-labs/learn-lab/blob/master/CHANGELOG.md
158
+ homepage_uri: https://github.com/flatiron-labs/learn-lab
159
+ source_code_uri: https://github.com/flatiron-labs/learn-lab
160
+ post_install_message:
161
+ rdoc_options: []
162
+ require_paths:
163
+ - lib
164
+ required_ruby_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: 2.5.0
169
+ required_rubygems_version: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">"
172
+ - !ruby/object:Gem::Version
173
+ version: 1.3.1
174
+ requirements: []
175
+ rubyforge_project:
176
+ rubygems_version: 2.7.6
177
+ signing_key:
178
+ specification_version: 4
179
+ summary: The command line interface for Flatiron School enterprise.
180
+ test_files: []