Commit f71fc932 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Also verify if extending would override a class method

Since extending a class means including on the singleton class of the
class, this should now complain this:

``` ruby
module M
  extend Gitlab::Utils::Override

  override :f
  def f
    super.succ
  end
end

class C
  extend M

  def self.f
    0
  end
end
```

It should complain because `C.f` wasn't calling `M#f`.
This should pass verification:

``` ruby
module M
  extend Gitlab::Utils::Override

  override :f
  def f
    super.succ
  end
end

class B
  def self.f
    0
  end
end

class C < B
  extend M
end
```

Because `C.f` would now call `M#f`, and `M#f` does override something.
parent 9c296194
...@@ -87,18 +87,28 @@ module Gitlab ...@@ -87,18 +87,28 @@ module Gitlab
end end
def included(base = nil) def included(base = nil)
return super if base.nil? # Rails concern, ignoring it super
queue_verification(base)
end
alias_method :prepended, :included
def extended(mod)
super super
queue_verification(mod.singleton_class)
end
def queue_verification(base)
return unless ENV['STATIC_VERIFICATION']
if base.is_a?(Class) # We could check for Class in `override` if base.is_a?(Class) # We could check for Class in `override`
# This could be `nil` if `override` was never called # This could be `nil` if `override` was never called
Override.extensions[self]&.add_class(base) Override.extensions[self]&.add_class(base)
end end
end end
alias_method :prepended, :included
def self.extensions def self.extensions
@extensions ||= {} @extensions ||= {}
end end
......
require 'spec_helper' require 'fast_spec_helper'
describe Gitlab::Utils::Override do describe Gitlab::Utils::Override do
let(:base) { Struct.new(:good) } let(:base) do
Struct.new(:good) do
def self.good
0
end
end
end
let(:derived) { Class.new(base).tap { |m| m.extend described_class } } let(:derived) { Class.new(base).tap { |m| m.extend described_class } }
let(:extension) { Module.new.tap { |m| m.extend described_class } } let(:extension) { Module.new.tap { |m| m.extend described_class } }
...@@ -9,6 +15,14 @@ describe Gitlab::Utils::Override do ...@@ -9,6 +15,14 @@ describe Gitlab::Utils::Override do
let(:prepending_class) { base.tap { |m| m.prepend extension } } let(:prepending_class) { base.tap { |m| m.prepend extension } }
let(:including_class) { base.tap { |m| m.include extension } } let(:including_class) { base.tap { |m| m.include extension } }
let(:prepending_class_methods) do
base.tap { |m| m.singleton_class.prepend extension }
end
let(:extending_class_methods) do
base.tap { |m| m.extend extension }
end
let(:klass) { subject } let(:klass) { subject }
def good(mod) def good(mod)
...@@ -36,7 +50,7 @@ describe Gitlab::Utils::Override do ...@@ -36,7 +50,7 @@ describe Gitlab::Utils::Override do
shared_examples 'checking as intended' do shared_examples 'checking as intended' do
it 'checks ok for overriding method' do it 'checks ok for overriding method' do
good(subject) good(subject)
result = klass.new(0).good result = instance.good
expect(result).to eq(1) expect(result).to eq(1)
described_class.verify! described_class.verify!
...@@ -45,7 +59,25 @@ describe Gitlab::Utils::Override do ...@@ -45,7 +59,25 @@ describe Gitlab::Utils::Override do
it 'raises NotImplementedError when it is not overriding anything' do it 'raises NotImplementedError when it is not overriding anything' do
expect do expect do
bad(subject) bad(subject)
klass.new(0).bad instance.bad
described_class.verify!
end.to raise_error(NotImplementedError)
end
end
shared_examples 'checking as intended, nothing was overridden' do
it 'raises NotImplementedError because it is not overriding it' do
expect do
good(subject)
instance.good
described_class.verify!
end.to raise_error(NotImplementedError)
end
it 'raises NotImplementedError when it is not overriding anything' do
expect do
bad(subject)
instance.bad
described_class.verify! described_class.verify!
end.to raise_error(NotImplementedError) end.to raise_error(NotImplementedError)
end end
...@@ -54,7 +86,7 @@ describe Gitlab::Utils::Override do ...@@ -54,7 +86,7 @@ describe Gitlab::Utils::Override do
shared_examples 'nothing happened' do shared_examples 'nothing happened' do
it 'does not complain when it is overriding something' do it 'does not complain when it is overriding something' do
good(subject) good(subject)
result = klass.new(0).good result = instance.good
expect(result).to eq(1) expect(result).to eq(1)
described_class.verify! described_class.verify!
...@@ -62,7 +94,7 @@ describe Gitlab::Utils::Override do ...@@ -62,7 +94,7 @@ describe Gitlab::Utils::Override do
it 'does not complain when it is not overriding anything' do it 'does not complain when it is not overriding anything' do
bad(subject) bad(subject)
result = klass.new(0).bad result = instance.bad
expect(result).to eq(true) expect(result).to eq(true)
described_class.verify! described_class.verify!
...@@ -75,83 +107,97 @@ describe Gitlab::Utils::Override do ...@@ -75,83 +107,97 @@ describe Gitlab::Utils::Override do
end end
describe '#override' do describe '#override' do
context 'when STATIC_VERIFICATION is set' do context 'when instance is klass.new(0)' do
before do let(:instance) { klass.new(0) }
stub_env('STATIC_VERIFICATION', 'true')
end
context 'when subject is a class' do context 'when STATIC_VERIFICATION is set' do
subject { derived } before do
stub_env('STATIC_VERIFICATION', 'true')
end
it_behaves_like 'checking as intended' context 'when subject is a class' do
end subject { derived }
it_behaves_like 'checking as intended'
end
context 'when subject is a module, and class is prepending it' do
subject { extension }
let(:klass) { prepending_class }
it_behaves_like 'checking as intended'
end
context 'when subject is a module, and class is prepending it' do context 'when subject is a module, and class is including it' do
subject { extension } subject { extension }
let(:klass) { prepending_class } let(:klass) { including_class }
it_behaves_like 'checking as intended' it_behaves_like 'checking as intended, nothing was overridden'
end
end end
context 'when subject is a module, and class is including it' do context 'when STATIC_VERIFICATION is not set' do
subject { extension } before do
let(:klass) { including_class } stub_env('STATIC_VERIFICATION', nil)
end
it 'raises NotImplementedError because it is not overriding it' do context 'when subject is a class' do
expect do subject { derived }
good(subject)
klass.new(0).good it_behaves_like 'nothing happened'
described_class.verify!
end.to raise_error(NotImplementedError)
end end
it 'raises NotImplementedError when it is not overriding anything' do context 'when subject is a module, and class is prepending it' do
expect do subject { extension }
bad(subject) let(:klass) { prepending_class }
klass.new(0).bad
described_class.verify! it_behaves_like 'nothing happened'
end.to raise_error(NotImplementedError)
end end
end
end
end
context 'when STATIC_VERIFICATION is not set' do context 'when subject is a module, and class is including it' do
before do subject { extension }
stub_env('STATIC_VERIFICATION', nil) let(:klass) { including_class }
end
context 'when subject is a class' do it 'does not complain when it is overriding something' do
subject { derived } good(subject)
result = instance.good
it_behaves_like 'nothing happened' expect(result).to eq(0)
end described_class.verify!
end
context 'when subject is a module, and class is prepending it' do it 'does not complain when it is not overriding anything' do
subject { extension } bad(subject)
let(:klass) { prepending_class } result = instance.bad
it_behaves_like 'nothing happened' expect(result).to eq(true)
described_class.verify!
end
end
end
end end
context 'when subject is a module, and class is including it' do context 'when instance is klass' do
subject { extension } let(:instance) { klass }
let(:klass) { including_class }
it 'does not complain when it is overriding something' do context 'when STATIC_VERIFICATION is set' do
good(subject) before do
result = klass.new(0).good stub_env('STATIC_VERIFICATION', 'true')
end
expect(result).to eq(0) context 'when subject is a module, and class is prepending it' do
described_class.verify! subject { extension }
end let(:klass) { prepending_class_methods }
it 'does not complain when it is not overriding anything' do it_behaves_like 'checking as intended'
bad(subject) end
result = klass.new(0).bad
expect(result).to eq(true) context 'when subject is a module, and class is extending it' do
described_class.verify! subject { extension }
let(:klass) { extending_class_methods }
it_behaves_like 'checking as intended, nothing was overridden'
end
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment