matlabunit-testing

Find and delete listeners


I am trying to devise a unit test for a MATLAB app that can run either in windowed or headless mode. The test runs through the program in headless mode and tries to detect if any windows get opened during process.

My thought on this was to attach a listener to the groot property CurrentFigure, and write a PostSet callback that increments a counter that keeps track of how many windows get opened. At the end the test then makes sure that the value is 0. For the record, this doesn't seem to work. Even though I have ShowHiddenHandles to 'on', it does not seem to catch modal windows. However, that is not my question.

My question is, I had an error in the test code between the place where I created the listener and the place where I delete it. Now, there is a listener attached to groot, but the listener handle variable is cleared, so I get really weird behavior every time I try to open a window. The test object opens back up and then throws an error at the listener callback.

How can I find and delete the listener attached to groot now that all original references to it have been removed from the workspace? So far the only method that works is to restart MATLAB, but that seems like an inefficient debugging method.


To reproduce the error, create the following test class:

classdef MCVtest < matlab.unittest.TestCase
    % Minimal, Complete, Verifiable example

    properties
        numOfFiguresCreated = 0;
    end

    methods
        function figureCreatedListener(testCase)
            testCase.numOfFiguresCreated = testCase.numOfFiguresCreated + 1;
        end
    end

    methods (Test)
        function testFiles(testCase)
            %Create Listener for this particular input file.
            listener = addlistener(groot, 'CurrentFigure', 'PostSet', @testCase.figureCreatedListener); %#ok<NASGU>

            error('Well, this sucks...')

            % delete Listener for this input file
            delete(listener) %#ok<UNRCH>

            % Verify That no graphics objects were created at all.
            testCase.verifyEqual(testCase.numOfFiguresCreated, 0);

        end
    end

end

From the command line:

>> suite = matlab.unittest.TestSuite.fromClass(?MCVtest)
>> results = suite.run

After the error:

>> figure
Error using MCVtest/figureCreatedListener
Too many input arguments.

Error in MCVtest>@(varargin)testCase.figureCreatedListener(varargin{:}) (line 17)
            listener = addlistener(groot, 'CurrentFigure', 'PostSet', @testCase.figureCreatedListener); %#ok<NASGU>

Of course, listener no longer exists, so I can't delete it. I've tried clear, clear all, and clear classes, in that order, and the listener still persists. The only way to clear it (so far as I've found) is to restart MATLAB.


Solution

  • You should be able to find the listeners by accessing the undocumented 'AutoListeners__' property of the Root (or any other graphics) object, which contains a cell array of the listeners.

    For example:

    a = addlistener(groot, 'CurrentFigure', 'PostSet', @(s,e)disp('hi'));
    tmp = groot;
    listeners = tmp.AutoListeners__;
    

    Which gives us:

    >> a == listeners{1}
    
    ans =
    
      logical
    
       1
    

    So we can do something like:

    for ii = 1:numel(listeners)
        delete(listeners{ii});
    end
    

    To remove all of the dangling listeners.

    Note that AutoListeners__ does not exist if there are no listeners for the object.


    Also note that MATLAB has 2 different listener implementations: addlistener, which binds the listener to the object and is removed when the object goes out of scope, and listener, which us unbound and will be removed when the listener goes out of scope.

    By utilizing listener instead of addlistener in your unit testing, you can avoid having dangling listeners if your cleanup is not run for whatever reason, in this case due to an error.