learn-lab 0.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []