gitlab-ce/spec/models/ml/experiment_spec.rb

195 lines
5.5 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ml::Experiment, feature_category: :mlops do
let_it_be(:exp) { create(:ml_experiments) }
let_it_be(:exp2) { create(:ml_experiments, project: exp.project) }
let_it_be(:model) { create(:ml_models, project: exp.project) }
let_it_be(:model_experiment) { model.default_experiment }
let(:iid) { exp.iid }
let(:exp_name) { exp.name }
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:user) }
it { is_expected.to have_many(:candidates) }
it { is_expected.to have_many(:metadata) }
it { is_expected.to belong_to(:model).class_name('Ml::Model') }
end
describe '#destroy' do
it 'allow experiment without model to be destroyed' do
experiment = create(:ml_experiments, project: exp.project)
expect { experiment.destroy! }.to change { Ml::Experiment.count }.by(-1)
end
it 'throws error when destroying experiment with model' do
experiment = create(:ml_models, project: exp.project).default_experiment
expect { experiment.destroy! }.to raise_error(ActiveRecord::ActiveRecordError)
expect(experiment.errors.full_messages).to include('Cannot delete an experiment associated to a model')
end
end
describe '.package_name' do
it { expect(exp.package_name).to eq("ml_experiment_#{exp.iid}") }
context 'when model belongs to package' do
it 'is the model name' do
expect(model_experiment.package_name).to eq(model.name)
end
end
end
describe '.for_model?' do
it 'is false if it is not the default experiment for a model' do
expect(exp.for_model?).to be(false)
end
it 'is true if it is not the default experiment for a model' do
expect(model_experiment.for_model?).to be(true)
end
end
describe '.by_project' do
subject { described_class.by_project(exp.project) }
it { is_expected.to match_array([exp, exp2, model_experiment]) }
end
describe '.including_project' do
subject { described_class.including_project }
it 'loads latest version' do
expect(subject.first.association_cached?(:project)).to be(true)
end
end
describe '.including_user' do
subject { described_class.including_user }
it 'loads latest version' do
expect(subject.first.association_cached?(:user)).to be(true)
end
end
describe '#by_project_id_and_iid' do
subject { described_class.by_project_id_and_iid(exp.project_id, iid) }
context 'if exists' do
it { is_expected.to eq(exp) }
end
context 'if does not exist' do
let(:iid) { non_existing_record_id }
it { is_expected.to be_nil }
end
end
describe '#by_project_id_and_name' do
subject { described_class.by_project_id_and_name(exp.project_id, exp_name) }
context 'if exists' do
it { is_expected.to eq(exp) }
end
context 'if does not exist' do
let(:exp_name) { 'hello' }
it { is_expected.to be_nil }
end
end
describe '.find_or_create' do
let(:name) { exp.name }
let(:project) { exp.project }
subject(:find_or_create) { described_class.find_or_create(project, name, exp.user) }
context 'when experiments exists' do
it 'fetches existing experiment', :aggregate_failures do
expect { find_or_create }.not_to change { Ml::Experiment.count }
expect(find_or_create).to eq(exp)
end
end
context 'when experiments does not exist' do
let(:name) { 'a new experiment' }
it 'creates the experiment', :aggregate_failures do
expect { find_or_create }.to change { Ml::Experiment.count }.by(1)
expect(find_or_create.name).to eq(name)
expect(find_or_create.user).to eq(exp.user)
expect(find_or_create.project).to eq(project)
end
end
context 'when experiment name exists but project is different' do
let(:project) { create(:project) }
it 'creates a model', :aggregate_failures do
expect { find_or_create }.to change { Ml::Experiment.count }.by(1)
expect(find_or_create.name).to eq(name)
expect(find_or_create.user).to eq(exp.user)
expect(find_or_create.project).to eq(project)
end
end
end
describe '#with_candidate_count' do
let_it_be(:exp3) do
create(:ml_experiments, project: exp.project).tap do |e|
create_list(:ml_candidates, 3, experiment: e, user: nil)
create(:ml_candidates, experiment: exp2, user: nil)
end
end
subject { described_class.with_candidate_count.to_h { |e| [e.id, e.candidate_count] } }
it 'fetches the candidate count', :aggregate_failures do
expect(subject[exp.id]).to eq(0)
expect(subject[exp2.id]).to eq(1)
expect(subject[exp3.id]).to eq(3)
end
end
describe '#package_for_experiment?' do
using RSpec::Parameterized::TableSyntax
subject { described_class.package_for_experiment?(package_name) }
where(:package_name, :id) do
'ml_experiment_1234' | true
'ml_experiment_1234abc' | false
'ml_experiment_abc' | false
'ml_experiment_' | false
'blah' | false
end
with_them do
it { is_expected.to be(id) }
end
end
describe "#exclude_experiments_for_models" do
subject { described_class.by_project(exp.project).exclude_experiments_for_models }
it 'excludes experiments that belongs to a model' do
is_expected.to match_array([exp, exp2])
end
end
describe '.count_for_project' do
subject { described_class.count_for_project(exp.project_id) }
it { is_expected.to be(3) }
end
end