Skip to content

Commit

Permalink
Merge fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
RickCarlino committed Mar 1, 2019
2 parents ff2f62c + 5e86e8d commit 88880c2
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 77 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Expand Up @@ -42,4 +42,4 @@ jobs:
- run:
name: Check Coveralls coverage on staging
command: |
sudo docker-compose run -e CIRCLE_SHA1="$CIRCLE_SHA1" web rake coverage:run
sudo docker-compose run -e CIRCLE_SHA1="$CIRCLE_SHA1" -e CIRCLE_BRANCH="$CIRCLE_BRANCH" web rake coverage:run
8 changes: 5 additions & 3 deletions frontend/config/__tests__/actions_test.ts
Expand Up @@ -31,21 +31,23 @@ import { ready, storeToken } from "../actions";
import { setToken, didLogin } from "../../auth/actions";
import { Session } from "../../session";
import { auth } from "../../__test_support__/fake_state/token";
import { fakeState } from "../../__test_support__/fake_state";

describe("Actions", () => {
it("calls didLogin()", () => {
jest.resetAllMocks();
const dispatch = jest.fn();
const getState = jest.fn(() => mockState);
const thunk = ready();
thunk(dispatch, getState);
thunk(dispatch, fakeState);
expect(setToken).toHaveBeenCalled();
});

it("Calls Session.clear() when missing auth", () => {
jest.resetAllMocks();
const dispatch = jest.fn();
const getState = jest.fn(() => ({}));
const state = fakeState();
delete state.auth;
const getState = () => state;
const thunk = ready();
thunk(dispatch, getState);
expect(Session.clear).toHaveBeenCalled();
Expand Down
@@ -1,5 +1,6 @@
import { toggleWebAppBool } from "../actions";
import { BooleanSetting } from "../../session_keys";
import { fakeState } from "../../__test_support__/fake_state";

jest.mock("../../api/crud", () => {
return { save: jest.fn(), edit: jest.fn() };
Expand All @@ -13,8 +14,7 @@ describe("toggleWebAppBool", () => {
it("toggles things", () => {
const action = toggleWebAppBool(BooleanSetting.show_first_party_farmware);
const dispatch = jest.fn();
const getState = jest.fn(() => ({ resources: { index: {} } }));
const kaboom = () => action(dispatch, getState);
const kaboom = () => action(dispatch, fakeState);
expect(kaboom).toThrowError("Toggled settings before app was loaded.");
});
});
17 changes: 8 additions & 9 deletions frontend/config_storage/__tests__/actions_test.ts
@@ -1,7 +1,10 @@
import { toggleWebAppBool, getWebAppConfigValue, setWebAppConfigValue } from "../actions";
import {
toggleWebAppBool, getWebAppConfigValue, setWebAppConfigValue
} from "../actions";
import { BooleanSetting, NumericSetting } from "../../session_keys";
import { edit, save } from "../../api/crud";
import { fakeWebAppConfig } from "../../__test_support__/fake_state/resources";
import { fakeState } from "../../__test_support__/fake_state";

jest.mock("../../api/crud", () => {
return { save: jest.fn(), edit: jest.fn() };
Expand All @@ -18,8 +21,7 @@ describe("toggleWebAppBool", () => {
it("toggles things", () => {
const action = toggleWebAppBool(BooleanSetting.show_first_party_farmware);
const dispatch = jest.fn();
const getState = jest.fn(() => ({ resources: { index: {} } }));
action(dispatch, getState);
action(dispatch, fakeState);
expect(edit).toHaveBeenCalledWith(mockConfig, {
show_first_party_farmware: true
});
Expand All @@ -28,8 +30,7 @@ describe("toggleWebAppBool", () => {
});

describe("getWebAppConfigValue", () => {
const getState = jest.fn(() => ({ resources: { index: {} } }));
const getValue = getWebAppConfigValue(getState);
const getValue = getWebAppConfigValue(fakeState);

it("gets a boolean setting value", () => {
expect(getValue(BooleanSetting.show_first_party_farmware)).toEqual(false);
Expand All @@ -41,10 +42,8 @@ describe("getWebAppConfigValue", () => {
});

describe("setWebAppConfigValue", () => {
const getState = jest.fn(() => ({ resources: { index: {} } }));

it("sets a numeric setting value", () => {
setWebAppConfigValue(NumericSetting.fun_log, 2)(jest.fn(), getState);
setWebAppConfigValue(NumericSetting.fun_log, 2)(jest.fn(), fakeState);
expect(edit).toHaveBeenCalledWith(mockConfig, { fun_log: 2 });
expect(save).toHaveBeenCalledWith(mockConfig.uuid);
});
Expand All @@ -53,7 +52,7 @@ describe("setWebAppConfigValue", () => {
// tslint:disable-next-line:no-any
mockConfig = undefined as any;
const action = () => setWebAppConfigValue(NumericSetting.fun_log, 1)(
jest.fn(), getState);
jest.fn(), fakeState);
expect(action).toThrowError("Changed settings before app was loaded.");
});
});
2 changes: 1 addition & 1 deletion frontend/devices/__tests__/actions_test.ts
Expand Up @@ -222,7 +222,7 @@ describe("settingToggle()", () => {
window.alert = jest.fn();
const msg = "this is an alert.";
actions.settingToggle(
"param_mov_nr_retry", jest.fn(() => ({ value: "" })),
"param_mov_nr_retry", jest.fn(() => ({ value: 1, consistent: true })),
msg)(jest.fn(), fakeState);
expect(window.alert).toHaveBeenCalledWith(msg);
});
Expand Down
2 changes: 1 addition & 1 deletion frontend/password_reset/__tests__/on_init_test.ts
Expand Up @@ -10,7 +10,7 @@ describe("onInit()", () => {
await onInit({}, jest.fn());
expect({}).toBeTruthy();
expect(render).toHaveBeenCalled();
const [calls] = (render as jest.Mock<{}>).mock.calls;
const [calls] = (render as jest.Mock).mock.calls;
expect(calls[0].type.name).toBe("PasswordReset");
done();
});
Expand Down
124 changes: 100 additions & 24 deletions lib/tasks/coverage.rake
@@ -1,14 +1,64 @@
COVERAGE_FILE_PATH = "./coverage_fe/index.html"
THRESHOLD = 0.001
REPO_URL = "https://api.github.com/repos/Farmbot/Farmbot-Web-App/git"\
"/refs/heads/staging"
REPO_URL = "https://api.github.com/repos/Farmbot/Farmbot-Web-App"
CURRENT_BRANCH = ENV.fetch("CIRCLE_BRANCH", "staging")
CURRENT_COMMIT = ENV.fetch("CIRCLE_SHA1", "")
CSS_SELECTOR = ".fraction"
FRACTION_DELIM = "/"

# Fetch JSON over HTTP. Rails probably already has a helper for this :shrug:
def open_json(url)
JSON.parse(open(url).read)
begin
JSON.parse(open(url).read)
rescue OpenURI::HTTPError => exception
puts exception.message
return {}
end
end

# Get pull request information from the GitHub API.
def fetch_pull_data()
if CURRENT_BRANCH.include? "/"
return open_json("#{REPO_URL}/pulls/#{CURRENT_BRANCH.split("/")[1]}")
end
return {}
end

# Determine the base branch of the current build.
def get_current_branch(pull_data)
pull_data.dig("base", "ref") || CURRENT_BRANCH
end

# Assemble the coverage data URL using the provided branch.
def coverage_url(branch)
commit = open_json("#{REPO_URL}/git/refs/heads/#{branch}").dig("object", "sha")
return "https://coveralls.io/builds/#{commit}.json"
end

# Fetch relevant remote coverage data.
def fetch_build_data(url)
build_data = open_json(url)
return {
branch: build_data["branch"],
commit: build_data["commit_sha"],
percent: build_data["covered_percent"]}
end

# <commit hash> on <username>:<branch>
def branch_info_string?(target, pull_data)
unless pull_data.dig(target, "sha").nil?
"#{pull_data.dig(target, "sha")} on #{pull_data.dig(target, "label")}"
end
end

# Print a coverage difference summary string.
def print_summary_text(build_percent, remote, pull_data)
diff = (build_percent - remote[:percent]).round(2)
direction = diff > 0 ? "increased" : "decreased"
description = diff == 0 ? "remained the same at" : "#{direction} (#{diff}%) to"
puts "Coverage #{description} #{build_percent.round(3)}%"\
" when pulling #{branch_info_string?("head", pull_data)}"\
" into #{branch_info_string?("base", pull_data) || remote[:branch]}."
end

def to_percent(pair)
Expand All @@ -18,6 +68,7 @@ end
namespace :coverage do
desc "Coveralls stats stopped working :("
task run: :environment do
# Fetch current build coverage data from the HTML summary.
statements, branches, functions, lines = Nokogiri::HTML(open(COVERAGE_FILE_PATH))
.css(CSS_SELECTOR)
.map(&:text)
Expand All @@ -30,41 +81,66 @@ namespace :coverage do
puts "Branches: #{to_percent(branches)}%"
puts "Functions: #{to_percent(functions)}%"
puts "Lines: #{to_percent(lines)}%"
puts

# Calculate an aggregate coverage percentage for the current build.
covered = lines.head + branches.head
total = lines.tail + branches.tail
build_percent = (covered / total) * 100
puts "Aggregate: #{build_percent.round(4)}%"
puts

latest_commit_staging = open_json(REPO_URL).dig("object", "sha")
puts "staging: #{latest_commit_staging}"
build_url = "https://coveralls.io/builds/#{latest_commit_staging}.json"
any_build_url = "https://coveralls.io/github/FarmBot/Farmbot-Web-App.json"
begin
staging_percent = open_json(build_url).fetch("covered_percent") ||
open_json(any_build_url).fetch("covered_percent")
rescue OpenURI::HTTPError => exception
puts exception.message
puts "Error getting coveralls data."
puts "Wait for staging build to finish and try again."
puts "If error continues, perhaps a blinky test failed the staging build."
staging_percent = 100
# Attempt to fetch remote build coverage data for the current branch.
pull_request_data = fetch_pull_data()
current_branch = get_current_branch(pull_request_data)
remote = fetch_build_data(coverage_url(current_branch))

if remote[:percent].nil? && CURRENT_COMMIT == remote[:commit]
puts "Coverage already calculated for #{remote[:branch]}."
puts "Using this build's data instead."
remote[:percent] = build_percent
end

if remote[:percent].nil? && current_branch != "staging"
puts "Error getting coveralls data for #{current_branch}."
puts "Attempting to use staging build coveralls data."
remote = fetch_build_data(coverage_url("staging"))
end

if CURRENT_COMMIT == latest_commit_staging
staging_percent = build_percent
if remote[:percent].nil?
puts "Error getting coveralls data for staging."
puts "Attempting to use latest build coveralls data."
latest_cov_url = "https://coveralls.io/github/FarmBot/Farmbot-Web-App.json"
remote = fetch_build_data(latest_cov_url)
end

diff = (build_percent - staging_percent)
if remote[:percent].nil?
puts "Error getting coveralls data."
puts "Wait for build to finish and try again or check for build errors."
puts "Using 100 instead of nil for remote coverage value."
remote = {branch: "N/A", commit: "", percent: 100}
end

# Adjust remote build data values for printing.
r = {
branch: (remote[:branch] + ' ' * 8)[0,8],
percent: remote[:percent].round(8),
commit: remote[:commit][0,8]}

# Calculate coverage difference between the current and previous build.
diff = (build_percent - remote[:percent])
pass = (diff > -THRESHOLD)

puts
puts "=" * 37
puts "COVERAGE RESULTS"
puts "This build: #{build_percent.round(8)}% #{CURRENT_COMMIT[0,8]}"
puts "Staging build: #{staging_percent.round(8)}% #{latest_commit_staging[0,8]}"
puts "This build: #{build_percent.round(8)}% #{CURRENT_COMMIT[0,8]}"
puts "#{r[:branch]} build: #{r[:percent]}% #{r[:commit]}"
puts "=" * 37
puts "Difference: #{diff.round(8)}%"
puts "Pass?: #{pass ? "yes" : "no"}"
puts "Difference: #{diff.round(8)}%"
puts "Pass? #{pass ? "yes" : "no"}"
puts

print_summary_text(build_percent, remote, pull_request_data)

exit pass ? 0 : 1

Expand Down
69 changes: 69 additions & 0 deletions lib/tasks/fe.rake
@@ -0,0 +1,69 @@
PACKAGE_JSON_FILE = "./package.json"
DEPS_KEY = "dependencies"
EXCLUDE = ["i18next", "@types/i18next"]

# Load package.json as JSON.
def load_package_json()
return JSON.parse(open(PACKAGE_JSON_FILE).read)
end

# Save JSON to package.json.
def save_package_json(json)
open(PACKAGE_JSON_FILE, "w") { |file|
file.write(JSON.pretty_generate(json))
file.puts
}
end

# Fetch latest versions for outdated dependencies.
def fetch_available_upgrades()
begin
latest_json = JSON.parse(`npm outdated --json`)
rescue JSON::ParserError => exception
latest_json = {}
end
latest_versions = {}
latest_json.each do |dep, data|
unless EXCLUDE.include?(dep) || data["latest"].nil?
latest_versions[dep] = data["latest"]
end
end
return latest_versions
end

# Install depdendency updates.
def install_updates
sh "sudo docker-compose run web npm install"
end

namespace :fe do
desc "Update frontend dependencies to the latest available."\
"This often causes breakage. Use only for development."
task update_deps: :environment do
puts "begin?"; if !user_typed?("developer"); puts "done."; exit end
available_upgrades = fetch_available_upgrades()
if available_upgrades.length > 0
max_key_length = available_upgrades.keys.max_by(&:length).length
package_json = load_package_json()

puts
puts "=" * 40
puts "#{PACKAGE_JSON_FILE} AVAILABLE UPDATES:"
available_upgrades.each do |dep, new_version|
current_version = package_json[DEPS_KEY][dep]
padding = ' ' * (max_key_length - dep.length)
puts " #{dep} #{padding} #{current_version} -> #{new_version}"
package_json[DEPS_KEY][dep] = new_version
end
puts "=" * 40

puts "Type 'save' to update #{PACKAGE_JSON_FILE}, enter to abort."
save_package_json(package_json) if user_typed?("save")

puts "Saved. Use 'sudo docker-compose run web npm install' to upgrade."
# install_updates if user_typed?("update")
else
puts "No updates available."
end
end
end

0 comments on commit 88880c2

Please sign in to comment.