Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-08-24 09:10:45 +00:00
parent 505cb2114d
commit 38d4874ab9
32 changed files with 363 additions and 130 deletions

View file

@ -11,3 +11,4 @@ export const TYPE_SCANNER_PROFILE = 'DastScannerProfile';
export const TYPE_SITE_PROFILE = 'DastSiteProfile';
export const TYPE_USER = 'User';
export const TYPE_VULNERABILITY = 'Vulnerability';
export const TYPE_NOTE = 'Note';

View file

@ -232,7 +232,9 @@ export function insertMarkdownText({
.join('\n');
}
} else if (tag.indexOf(textPlaceholder) > -1) {
textToInsert = tag.replace(textPlaceholder, () => selected.replace(/\\n/g, '\n'));
textToInsert = tag.replace(textPlaceholder, () =>
selected.replace(/\\n/g, '\n').replace(/\/(n|t|r)/g, '\\$1'),
);
} else {
textToInsert = String(startChar) + tag + selected + (wrap ? tag : '');
}

View file

@ -15,6 +15,14 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import MarkdownHeader from './header.vue';
import MarkdownToolbar from './toolbar.vue';
function cleanUpLine(content) {
return unescape(
stripHtml(content)
.replace(/\\(n|t|r)/g, '/$1')
.replace(/\n/g, ''),
);
}
export default {
components: {
GfmAutocomplete,
@ -129,7 +137,7 @@ export default {
return text;
}
return unescape(stripHtml(richText).replace(/\n/g, ''));
return cleanUpLine(richText);
})
.join('\\n');
}
@ -141,7 +149,7 @@ export default {
return text;
}
return unescape(stripHtml(richText).replace(/\n/g, ''));
return cleanUpLine(richText);
}
return '';
@ -272,6 +280,7 @@ export default {
:can-suggest="canSuggest"
:show-suggest-popover="showSuggestPopover"
:suggestion-start-index="suggestionsStartIndex"
data-testid="markdownHeader"
@preview-markdown="showPreviewTab"
@write-markdown="showWriteTab"
@handleSuggestDismissed="() => $emit('handleSuggestDismissed')"

View file

@ -297,7 +297,10 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def index_params
params.permit(:scope, :username, :ref, :status)
permitted_params = [:scope, :username, :ref, :status]
permitted_params << :source if Feature.enabled?(:pipeline_source_filter, project, default_enabled: :yaml)
params.permit(*permitted_params)
end
def enable_code_quality_walkthrough_experiment

View file

@ -306,7 +306,9 @@ module Ci
end
after_transition pending: :running do |build|
build.deployment&.run
Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
build.deployment&.run
end
build.run_after_commit do
build.pipeline.persistent_ref.create
@ -328,7 +330,9 @@ module Ci
end
after_transition any => [:success] do |build|
build.deployment&.succeed
Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
build.deployment&.succeed
end
build.run_after_commit do
BuildSuccessWorker.perform_async(id)
@ -341,7 +345,9 @@ module Ci
next unless build.deployment
begin
build.deployment.drop!
Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
build.deployment.drop!
end
rescue StandardError => e
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, build_id: build.id)
end
@ -362,10 +368,12 @@ module Ci
end
after_transition any => [:skipped, :canceled] do |build, transition|
if transition.to_name == :skipped
build.deployment&.skip
else
build.deployment&.cancel
Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
if transition.to_name == :skipped
build.deployment&.skip
else
build.deployment&.cancel
end
end
end
end

View file

@ -83,6 +83,10 @@ module Featurable
end
end
included do
validate :allowed_access_levels
end
def access_level(feature)
public_send(self.class.access_level_attribute(feature)) # rubocop:disable GitlabSecurity/PublicSend
end
@ -94,4 +98,21 @@ module Featurable
def string_access_level(feature)
self.class.str_from_access_level(access_level(feature))
end
private
def allowed_access_levels
validator = lambda do |field|
level = public_send(field) || ENABLED # rubocop:disable GitlabSecurity/PublicSend
not_allowed = level > ENABLED
self.errors.add(field, "cannot have public visibility level") if not_allowed
end
(self.class.available_features - feature_validation_exclusion).each {|f| validator.call("#{f}_access_level")}
end
# Features that we should exclude from the validation
def feature_validation_exclusion
[]
end
end

View file

@ -54,7 +54,6 @@ class ProjectFeature < ApplicationRecord
validates :project, presence: true
validate :repository_children_level
validate :allowed_access_levels
default_value_for :builds_access_level, value: ENABLED, allows_nil: false
default_value_for :issues_access_level, value: ENABLED, allows_nil: false
@ -110,17 +109,6 @@ class ProjectFeature < ApplicationRecord
%i(merge_requests_access_level builds_access_level).each(&validator)
end
# Validates access level for other than pages cannot be PUBLIC
def allowed_access_levels
validator = lambda do |field|
level = public_send(field) || ENABLED # rubocop:disable GitlabSecurity/PublicSend
not_allowed = level > ENABLED
self.errors.add(field, "cannot have public visibility level") if not_allowed
end
(FEATURES - %i(pages)).each {|f| validator.call("#{f}_access_level")}
end
def get_permission(user, feature)
case access_level(feature)
when DISABLED
@ -142,6 +130,10 @@ class ProjectFeature < ApplicationRecord
project.team.member?(user, ProjectFeature.required_minimum_access_level(feature))
end
def feature_validation_exclusion
%i(pages)
end
end
ProjectFeature.prepend_mod_with('ProjectFeature')

View file

@ -10,16 +10,9 @@ module Ci
private
def process_subsequent_jobs(processable)
if Feature.enabled?(:ci_same_stage_job_needs, processable.project, default_enabled: :yaml)
(stage_dependent_jobs(processable) | needs_dependent_jobs(processable))
.each do |processable|
process(processable)
end
else
skipped_jobs(processable).after_stage(processable.stage_idx)
.find_each do |job|
process(job)
end
(stage_dependent_jobs(processable) | needs_dependent_jobs(processable))
.each do |processable|
process(processable)
end
end

View file

@ -1,8 +0,0 @@
---
name: ci_same_stage_job_needs
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59668
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/328253
milestone: '14.1'
type: development
group: group::pipeline authoring
default_enabled: true

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
class RemoveAllowEditingCommitMessagesFromProjectSettings < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
def up
with_lock_retries do
remove_column :project_settings, :allow_editing_commit_messages
end
end
def down
with_lock_retries do
add_column :project_settings, :allow_editing_commit_messages, :boolean, default: false, null: false
end
end
end

View file

@ -0,0 +1 @@
b85ef326056bb152d527e34b49caa3c40ee8685c3b14654992246c6adf082f8c

View file

@ -17343,7 +17343,6 @@ CREATE TABLE project_settings (
squash_option smallint DEFAULT 3,
has_confluence boolean DEFAULT false NOT NULL,
has_vulnerabilities boolean DEFAULT false NOT NULL,
allow_editing_commit_messages boolean DEFAULT false NOT NULL,
prevent_merge_without_jira_issue boolean DEFAULT false NOT NULL,
cve_id_request_enabled boolean DEFAULT true NOT NULL,
mr_default_target_self boolean DEFAULT false NOT NULL,

View file

@ -55,6 +55,12 @@ zcat @400000006026b71d1a7af804.s | (head -1; tail -1) | jq '.time'
zcat some_json.log.25.gz | (head -1; tail -1) | jq '.time'
```
#### Get activity for correlation ID across multiple JSON logs in chronological order
```shell
grep -hR <correlationID> | jq -c -R 'fromjson?' | jq -C -s 'sort_by(.time)' | less -R
```
### Parsing `production_json.log` and `api_json.log`
#### Find all requests with a 5XX status code

View file

@ -1594,8 +1594,7 @@ production:
- In [GitLab 14.1 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/30632) you
can refer to jobs in the same stage as the job you are configuring. This feature is
enabled on GitLab.com and ready for production use. On self-managed [GitLab 14.2 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/30632)
this feature is available by default. To hide the feature, ask an administrator to
[disable the `ci_same_stage_job_needs` flag](../../administration/feature_flags.md).
this feature is available by default.
- In GitLab 14.0 and older, you can only refer to jobs in earlier stages.
- In GitLab 13.9 and older, if `needs:` refers to a job that might not be added to
a pipeline because of `only`, `except`, or `rules`, the pipeline might fail to create.

View file

@ -15,12 +15,7 @@ module Gitlab
@context = context
@pipeline = context.pipeline
@seed_attributes = attributes
@stages_for_needs_lookup = if Feature.enabled?(:ci_same_stage_job_needs, @pipeline.project, default_enabled: :yaml)
(previous_stages + [current_stage]).compact
else
previous_stages
end
@stages_for_needs_lookup = (previous_stages + [current_stage]).compact
@needs_attributes = dig(:needs_attributes)
@resource_group_key = attributes.delete(:resource_group_key)
@job_variables = @seed_attributes.delete(:job_variables)

View file

@ -47,9 +47,7 @@ module Gitlab
validate_job!(name, job)
end
if ::Feature.enabled?(:ci_same_stage_job_needs, @opts[:project], default_enabled: :yaml)
YamlProcessor::Dag.check_circular_dependencies!(@jobs)
end
YamlProcessor::Dag.check_circular_dependencies!(@jobs)
end
def validate_job!(name, job)
@ -103,16 +101,8 @@ module Gitlab
job_stage_index = stage_index(name)
dependency_stage_index = stage_index(dependency)
if ::Feature.enabled?(:ci_same_stage_job_needs, @opts[:project], default_enabled: :yaml)
unless dependency_stage_index.present? && dependency_stage_index <= job_stage_index
error!("#{name} job: #{dependency_type} #{dependency} is not defined in current or prior stages")
end
else
# A dependency might be defined later in the configuration
# with a stage that does not exist
unless dependency_stage_index.present? && dependency_stage_index < job_stage_index
error!("#{name} job: #{dependency_type} #{dependency} is not defined in prior stages")
end
unless dependency_stage_index.present? && dependency_stage_index <= job_stage_index
error!("#{name} job: #{dependency_type} #{dependency} is not defined in current or prior stages")
end
end

View file

@ -147,9 +147,16 @@ module Gitlab
# spec/support/database/prevent_cross_joins.rb
end
# This method will allow cross database modifications within the block
# Example:
#
# allow_cross_database_modification_within_transaction(url: 'url-to-an-issue') do
# create(:build) # inserts ci_build and project record in one transaction
# end
def self.allow_cross_database_modification_within_transaction(url:)
# this method is implemented in:
# this method will be overridden in:
# spec/support/database/cross_database_modification_check.rb
yield
end
def self.add_post_migrate_path_to_rails(force: false)

View file

@ -0,0 +1,30 @@
# frozen_string_literal: true
module Gitlab
module Pagination
module Keyset
class CursorBasedRequestContext
attr_reader :request
delegate :params, :header, to: :request
def initialize(request)
@request = request
end
def per_page
params[:per_page]
end
def cursor
params[:cursor]
end
def apply_headers(cursor_for_next_page)
Gitlab::Pagination::Keyset::HeaderBuilder
.new(self)
.add_next_page_header({ cursor: cursor_for_next_page })
end
end
end
end
end

View file

@ -0,0 +1,38 @@
# frozen_string_literal: true
module Gitlab
module Pagination
module Keyset
class CursorPager < Gitlab::Pagination::Base
attr_reader :cursor_based_request_context, :paginator
def initialize(cursor_based_request_context)
@cursor_based_request_context = cursor_based_request_context
end
def paginate(relation)
@paginator ||= relation.keyset_paginate(
per_page: cursor_based_request_context.per_page,
cursor: cursor_based_request_context.cursor
)
paginator.records
end
def finalize(_records = [])
# can be called only after executing `paginate(relation)`
apply_headers
end
private
def apply_headers
return unless paginator.has_next_page?
cursor_based_request_context
.apply_headers(paginator.cursor_for_next_page)
end
end
end
end
end

View file

@ -88,6 +88,25 @@ describe('init markdown', () => {
expect(textArea.value).toEqual(`${initialValue}\n- `);
});
it('unescapes new line characters', () => {
const initialValue = '';
textArea.value = initialValue;
textArea.selectionStart = 0;
textArea.selectionEnd = 0;
insertMarkdownText({
textArea,
text: textArea.value,
tag: '```suggestion:-0+0\n{text}\n```',
blockTag: true,
selected: '# Does not parse the /n currently.',
wrap: false,
});
expect(textArea.value).toContain('# Does not parse the \\n currently.');
});
it('inserts the tag on the same line if the current line only contains spaces', () => {
const initialValue = ' ';

View file

@ -28,6 +28,7 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] =
data-uploads-path=""
>
<markdown-header-stub
data-testid="markdownHeader"
linecontent=""
suggestionstartindex="0"
/>

View file

@ -32,7 +32,7 @@ describe('Markdown field component', () => {
axiosMock.restore();
});
function createSubject() {
function createSubject(lines = []) {
// We actually mount a wrapper component so that we can force Vue to rerender classes in order to test a regression
// caused by mixing Vanilla JS and Vue.
subject = mount(
@ -60,6 +60,7 @@ describe('Markdown field component', () => {
markdownPreviewPath,
isSubmitting: false,
textareaValue,
lines,
},
},
);
@ -243,4 +244,14 @@ describe('Markdown field component', () => {
});
});
});
describe('suggestions', () => {
it('escapes new line characters', () => {
createSubject([{ rich_text: 'hello world\\n' }]);
expect(subject.find('[data-testid="markdownHeader"]').props('lineContent')).toBe(
'hello world/n',
);
});
});
});

View file

@ -1140,16 +1140,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
it 'does not have errors' do
expect(subject.errors).to be_empty
end
context 'when ci_same_stage_job_needs FF is disabled' do
before do
stub_feature_flags(ci_same_stage_job_needs: false)
end
it 'has errors' do
expect(subject.errors).to contain_exactly("'rspec' job needs 'build' job, but 'build' is not in any previous stage")
end
end
end
context 'when using 101 needs' do

View file

@ -34,10 +34,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Pipeline do
described_class.new(seed_context, stages_attributes)
end
before do
stub_feature_flags(ci_same_stage_job_needs: false)
end
describe '#stages' do
it 'returns the stage resources' do
stages = seed.stages

View file

@ -590,14 +590,6 @@ module Gitlab
end
it_behaves_like 'has warnings and expected error', /build job: need test is not defined in current or prior stages/
context 'with ci_same_stage_job_needs FF disabled' do
before do
stub_feature_flags(ci_same_stage_job_needs: false)
end
it_behaves_like 'has warnings and expected error', /build job: need test is not defined in prior stages/
end
end
end
end
@ -1809,14 +1801,6 @@ module Gitlab
let(:dependencies) { ['deploy'] }
it_behaves_like 'returns errors', 'test1 job: dependency deploy is not defined in current or prior stages'
context 'with ci_same_stage_job_needs FF disabled' do
before do
stub_feature_flags(ci_same_stage_job_needs: false)
end
it_behaves_like 'returns errors', 'test1 job: dependency deploy is not defined in prior stages'
end
end
context 'when a job depends on another job that references a not-yet defined stage' do
@ -2053,14 +2037,6 @@ module Gitlab
let(:needs) { ['deploy'] }
it_behaves_like 'returns errors', 'test1 job: need deploy is not defined in current or prior stages'
context 'with ci_same_stage_job_needs FF disabled' do
before do
stub_feature_flags(ci_same_stage_job_needs: false)
end
it_behaves_like 'returns errors', 'test1 job: need deploy is not defined in prior stages'
end
end
context 'needs and dependencies that are mismatching' do

View file

@ -0,0 +1,60 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Pagination::Keyset::CursorBasedRequestContext do
let(:params) { { per_page: 2, cursor: 'eyJuYW1lIjoiR2l0TGFiIEluc3RhbmNlIiwiaWQiOiI1MiIsIl9rZCI6Im4ifQ==' } }
let(:request) { double('request', params: params) }
describe '#per_page' do
subject(:per_page) { described_class.new(request).per_page }
it { is_expected.to eq 2 }
end
describe '#cursor' do
subject(:cursor) { described_class.new(request).cursor }
it { is_expected.to eq 'eyJuYW1lIjoiR2l0TGFiIEluc3RhbmNlIiwiaWQiOiI1MiIsIl9rZCI6Im4ifQ==' }
end
describe '#apply_headers' do
let(:request) { double('request', url: "http://#{Gitlab.config.gitlab.host}/api/v4/projects?per_page=3", params: params) }
let(:params) { { per_page: 3 } }
let(:cursor_for_next_page) { 'eyJuYW1lIjoiSDVicCIsImlkIjoiMjgiLCJfa2QiOiJuIn0=' }
subject(:apply_headers) { described_class.new(request).apply_headers(cursor_for_next_page) }
it 'sets Link header with same host/path as the original request' do
orig_uri = URI.parse(request.url)
expect(request).to receive(:header).once do |name, header|
first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures
uri = URI.parse(first_link)
expect(name).to eq('Link')
expect(uri.host).to eq(orig_uri.host)
expect(uri.path).to eq(orig_uri.path)
end
apply_headers
end
it 'sets Link header with a cursor to the next page' do
orig_uri = URI.parse(request.url)
expect(request).to receive(:header).once do |name, header|
first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures
query = CGI.parse(URI.parse(first_link).query)
expect(name).to eq('Link')
expect(query.except('cursor')).to eq(CGI.parse(orig_uri.query).except('cursor'))
expect(query['cursor']).to eq([cursor_for_next_page])
end
apply_headers
end
end
end

View file

@ -0,0 +1,63 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Pagination::Keyset::CursorPager do
let(:relation) { Group.all.order(:name, :id) }
let(:per_page) { 3 }
let(:params) { { cursor: nil, per_page: per_page } }
let(:request) { double('request', params: params) }
let(:cursor_based_request_context) { Gitlab::Pagination::Keyset::CursorBasedRequestContext.new(request) }
before_all do
create_list(:group, 7)
end
describe '#paginate' do
subject(:paginated_result) { described_class.new(cursor_based_request_context).paginate(relation) }
it 'returns the limited relation' do
expect(paginated_result).to eq(relation.limit(per_page))
end
end
describe '#finalize' do
subject(:finalize) do
service = described_class.new(cursor_based_request_context)
# we need to do this because `finalize` can only be called
# after `paginate` is called. Otherwise the `paginator` object won't be set.
service.paginate(relation)
service.finalize
end
it 'passes information about next page to request' do
cursor_for_next_page = relation.keyset_paginate(**params).cursor_for_next_page
expect_next_instance_of(Gitlab::Pagination::Keyset::HeaderBuilder, cursor_based_request_context) do |builder|
expect(builder).to receive(:add_next_page_header).with({ cursor: cursor_for_next_page })
end
finalize
end
context 'when retrieving the last page' do
let(:relation) { Group.where('id > ?', Group.maximum(:id) - per_page).order(:name, :id) }
it 'does not build information about the next page' do
expect(Gitlab::Pagination::Keyset::HeaderBuilder).not_to receive(:new)
finalize
end
end
context 'when retrieving an empty page' do
let(:relation) { Group.where('id > ?', Group.maximum(:id) + 1).order(:name, :id) }
it 'does not build information about the next page' do
expect(Gitlab::Pagination::Keyset::HeaderBuilder).not_to receive(:new)
finalize
end
end
end
end

View file

@ -1307,7 +1307,9 @@ RSpec.describe Ci::Build do
shared_examples_for 'avoid deadlock' do
it 'executes UPDATE in the right order' do
recorded = ActiveRecord::QueryRecorder.new { subject }
recorded = with_cross_database_modification_prevented do
ActiveRecord::QueryRecorder.new { subject }
end
index_for_build = recorded.log.index { |l| l.include?("UPDATE \"ci_builds\"") }
index_for_deployment = recorded.log.index { |l| l.include?("UPDATE \"deployments\"") }
@ -1322,7 +1324,9 @@ RSpec.describe Ci::Build do
it_behaves_like 'avoid deadlock'
it 'transits deployment status to running' do
subject
with_cross_database_modification_prevented do
subject
end
expect(deployment).to be_running
end
@ -1340,7 +1344,9 @@ RSpec.describe Ci::Build do
it_behaves_like 'calling proper BuildFinishedWorker'
it 'transits deployment status to success' do
subject
with_cross_database_modification_prevented do
subject
end
expect(deployment).to be_success
end
@ -1353,7 +1359,9 @@ RSpec.describe Ci::Build do
it_behaves_like 'calling proper BuildFinishedWorker'
it 'transits deployment status to failed' do
subject
with_cross_database_modification_prevented do
subject
end
expect(deployment).to be_failed
end
@ -1365,7 +1373,9 @@ RSpec.describe Ci::Build do
it_behaves_like 'avoid deadlock'
it 'transits deployment status to skipped' do
subject
with_cross_database_modification_prevented do
subject
end
expect(deployment).to be_skipped
end
@ -1378,7 +1388,9 @@ RSpec.describe Ci::Build do
it_behaves_like 'calling proper BuildFinishedWorker'
it 'transits deployment status to canceled' do
subject
with_cross_database_modification_prevented do
subject
end
expect(deployment).to be_canceled
end

View file

@ -30,8 +30,11 @@ RSpec.describe Featurable do
describe '.set_available_features' do
let!(:klass) do
Class.new do
Class.new(ApplicationRecord) do
include Featurable
self.table_name = 'project_features'
set_available_features %i(feature1 feature2)
def feature1_access_level

View file

@ -41,18 +41,15 @@ RSpec.describe ProjectFeature do
end
end
context 'public features' do
features = ProjectFeature::FEATURES - %i(pages)
it_behaves_like 'access level validation', ProjectFeature::FEATURES - %i(pages) do
let(:container_features) { project.project_feature }
end
features.each do |feature|
it "does not allow public access level for #{feature}" do
project_feature = project.project_feature
field = "#{feature}_access_level".to_sym
project_feature.update_attribute(field, ProjectFeature::PUBLIC)
it 'allows public access level for :pages feature' do
project_feature = project.project_feature
project_feature.pages_access_level = ProjectFeature::PUBLIC
expect(project_feature.valid?).to be_falsy, "#{field} failed"
end
end
expect(project_feature.valid?).to be_truthy
end
describe 'default pages access level' do

View file

@ -44,16 +44,6 @@ RSpec.describe Ci::AfterRequeueJobService do
it 'marks subsequent skipped jobs as processable' do
expect { execute_service }.to change { test4.reload.status }.from('skipped').to('created')
end
context 'with ci_same_stage_job_needs FF disabled' do
before do
stub_feature_flags(ci_same_stage_job_needs: false)
end
it 'does nothing with the build' do
expect { execute_service }.not_to change { test4.reload.status }
end
end
end
context 'when the pipeline is a downstream pipeline and the bridge is depended' do

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
RSpec.shared_examples 'access level validation' do |features|
features.each do |feature|
it "does not allow public access level for #{feature}" do
field = "#{feature}_access_level".to_sym
container_features.update_attribute(field, ProjectFeature::PUBLIC)
expect(container_features.valid?).to be_falsy, "#{field} failed"
end
end
end